问题描述:使用的是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
既然选择了新版本,那就用第二种吧。