深入分析mysql 6.0.6 和 activiti 6.0.0自动创建表失败的问题

2 篇文章 0 订阅
1 篇文章 0 订阅

问题描述:使用的是mysql数据库,有两个数据库,一个dev,一个test。两个数据库除了数据库名不一样,其他信息均一样。activiti表的生成策略是,如果没有创建,有则更新。当dev或test数据库均未有activiti表时,连接任意一个均能成功,当任意一个有activiti表时,连接另外一个库,启动均报错,比如,dev有activiti表,连接test库,则错误信息如下:

### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Table 'test.ACT_GE_PROPERTY' doesn't exist
### The error may exist in org/activiti/db/mapping/entity/Property.xml
### The error may involve org.activiti.engine.impl.persistence.entity.PropertyEntityImpl.selectProperty-Inline
### The error occurred while setting parameters
### SQL: select * from ACT_GE_PROPERTY where NAME_ = ?
### Cause: java.sql.SQLSyntaxErrorException: Table 'test.ACT_GE_PROPERTY' doesn't exist

问题分析:从错误上来看,是activiti查询的时候,"串库"了,认为test库里已经有activiti的表了,所以会执行查询操作。而实际上这些表是在dev库里。为什么会出现这样的情况呢,让我们深入activiti的源码看看吧。以下是activiti 6.0 DBSqlSession的更新逻辑。代码

public String dbSchemaUpdate() {
        String feedback = null;
        boolean isUpgradeNeeded = false;
        int matchingVersionIndex = -1;
        if (this.isEngineTablePresent()) {//判断的核心方法,看是否需要更新或创建
           //更新操作,省略
        } else {
            this.dbSchemaCreateEngine();
        }
        return feedback;
    }
public boolean isEngineTablePresent() {
        return this.isTablePresent("ACT_RU_EXECUTION"); //通过act_ru_execution判断是否存在activiti表
    }
public boolean isTablePresent(String tableName) {
          //省略
            boolean var8;
            try {
		//getTables是通过jdbc来实现的,这里的版本是mysql6.0.6
                tables = databaseMetaData.getTables(catalog, schema, tableName, JDBC_METADATA_TABLE_TYPES);
                var8 = tables.next();
            } finally {
                try {
                    tables.close();
                } catch (Exception var16) {
                    log.error("Error closing meta data tables", var16);
                }
            }
            return var8;
    }

从上可以看出,核心关键在于getTables方法,在mysql 6.0.6方法中的getTables核心逻辑代码如下:

public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, final String[] types) throws SQLException {
        try {
	    //以下是mysql 6.0.6部分源码逻辑,感兴趣的朋友可以自己去看完整源码
            final SortedMap<DatabaseMetaData.TableMetaDataKey, Row> sortedRows = new TreeMap();
            ArrayList<Row> tuples = new ArrayList();
            final Statement stmt = this.conn.getMetadataSafeStatement();
            String tmpCat = "";
            if (catalog != null && catalog.length() != 0) {
                tmpCat = catalog;
            } else if (this.nullCatalogMeansCurrent) {
	        //问题的关键点,this.nullCatalogMeansCurrent为false,导致tmpCat为空,tmpCat理应是数据库名
	        //而nullCatalogMeansCurrent在6.0.6中默认为false,赋值是在构造函数里赋的值
                tmpCat = this.database;
            }
            List<String> parseList = StringUtils.splitDBdotName(tableNamePattern, tmpCat, this.quotedId, this.conn.isNoBackslashEscapesSet());
            final String tableNamePat;
            if (parseList.size() == 2) {
                tableNamePat = (String)parseList.get(1);
            } else {
                tableNamePat = tableNamePattern;
            }
            try {
                (new IterateBlock<String>(this.getCatalogIterator(catalog)) {
		   // catalogStr 是数据库名,如果nullCatalogMeansCurrent为false,则遍历所有的数据库链接
		   // 如果nullCatalogMeansCurrent为true,则使用指定的数据库名,不遍历。
                    void forEach(String catalogStr) throws SQLException {
			//排除information_schema, mysql 和 performance_schema 系统DB
                        boolean operatingOnSystemDB = "information_schema".equalsIgnoreCase(catalogStr) || "mysql".equalsIgnoreCase(catalogStr) || "performance_schema".equalsIgnoreCase(catalogStr);
                        ResultSet results = null;
			try {
			    results = stmt.executeQuery("SHOW FULL TABLES FROM " + StringUtils.quoteIdentifier(catalogStr, DatabaseMetaData.this.quotedId, DatabaseMetaData.this.pedantic) + " LIKE " + StringUtils.quoteIdentifier(tableNamePat, "'", true));
			} catch (SQLException var27) {
			    if (!"08S01".equals(var27.getSQLState())) {
				return;
			}
		//doForAll(),遍历数据库链接,如果nullCatalogMeansCurrent为true,不遍历。
                }).doForAll();
            } finally {
                if (stmt != null) {
                    stmt.close();
                }
            }
            tuples.addAll(sortedRows.values());
            ResultSet tables = this.resultSetFactory.createFromResultsetRows(1007, 1004, new ResultsetRowsStatic(tuples, this.createTablesFields()));
            return tables;
        } catch (CJException var16) {
            throw SQLExceptionsMapping.translateException(var16, this.getExceptionInterceptor());
        }
    }

从上面源码可以看出,当nullCatalogMeansCurrent为false时,mysql驱动会遍历当前链接的所有表,执行以下语句,判断是否存在Activiti表。这样就会出现文章开头所讲的“串库”的效果。

SHOW FULL TABLES FROM `TABLE_NAME` LIKE 'ACT_RU_EXECUTION'

如果nullCatalogMeansCurrent为true,则使用指定的数据库来执行查询语句,不会遍历。再来看看nullCatalogMeansCurrent的赋值逻辑:

protected DatabaseMetaData(JdbcConnection connToSet, String databaseToSet, ResultSetFactory resultSetFactory) {
        //省略
        this.nullCatalogMeansCurrent = ((Boolean)this.conn.getPropertySet().getBooleanReadableProperty("nullCatalogMeansCurrent").getValue()).booleanValue();
    }

可以看出是从property里取的值,也就是url地址栏后面,也就是说在地址栏nullCatalogMeansCurrent=true即可。

另外,我又试了下降低mysql的版本,改成5.1.46,发现没有问题,看了一下它的实现代码,发现nullCatalogMeansCurrent属性默认为true,在ConnectionPropertiesImpl类的构造函数里赋的值,感兴趣的朋友可以自行去查看。代码逻辑如下:

public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, final String[] types) throws SQLException {
	   //这里只罗列部分代码
        final SortedMap<DatabaseMetaData.TableMetaDataKey, ResultSetRow> sortedRows = new TreeMap();
        ArrayList<ResultSetRow> tuples = new ArrayList();
        final Statement stmt = this.conn.getMetadataSafeStatement();
        String tmpCat = "";
        if (catalog != null && catalog.length() != 0) {
            tmpCat = catalog;
        } else if (this.conn.getNullCatalogMeansCurrent()) {
	    //从Connection里面获取的nullCatalogMeansCurrent
	    //这里的赋值操作是ConnectionPropertiesImpl,默认赋的是true。
            tmpCat = this.database;
        }
        tuples.addAll(sortedRows.values());
        ResultSet tables = this.buildResultSet(this.createTablesFields(), tuples);
        return tables;
    }

问题总结:

1. 从mysql-connector-java 5.x 到 6.x,nullCatalogMeansCurrent属性由原来的默认true改为了false。

2. true 使用指定的数据库进行查询。优先取当前传入的数据库名,其次取当前链接的数据库名。

3. false 代表遍历当前链接下的所有数据库进行查询,官网说的是按照目录查询,说实话,我刚开始看到这官方说法,我是黑人问号脸的。后来通过调试源码才明白,其实就是遍历当前链接下的所有数据库(information_schema, mysql 和 performance_schema 这三个系统DB,虽然在最终结果里进行了排除,但是依然进行了查询,还因此做了很多逻辑处理,这块感觉可以进行优化,比如可以另加一个参数,只遍历自定义的库等等),这也解释了一开始遇到的“串库”问题。

解决方法:

1. 将mysql版本降为5.x

2. 在url后面加上nullCatalogMeansCurrent=true

既然选择了新版本,那就用第二种吧。



  • 21
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值