camunda启动异常Table 'activiti.act_ge_property' doesn't exist

环境:mysql-connector-java-8.0.21.jar

问题:如果jdbc连接ip下已经有一个schema包含activiti表,再新建schema就异常。

         mysql版本5.x 和 8.x结果一样。

 

解决方式一:降低mysql-connector版本至5.x(同时也应该使用mysql 5.x)

解决方式二:在连接语句增加 nullCatalogMeansCurrent=true

 

==========

参考:https://blog.csdn.net/jiaoshaoping/article/details/80748065

问题描述:使用的是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

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

posted @ 2021-08-18 10:30  jason47  阅读(2183)  评论(2编辑  收藏  举报