一个 Flowable 项目在启动时抛出表不存在的错误。
这是一个 Spring Boot 3 项目,使用了 Flowable 7.0.1,出现问题的是测试环境的数据库,MySQL 5.7.41。
很奇怪,因为默认配置下 Flowable 启动时会检查数据库中是否存在相关的表,没有则会创建。搜索一番,有人提到在 JDBC 连接参数中加上 nullCatalogMeansCurrent=true
可解,在 MySQL Connector/J 8.0 中这个参数的默认值改成了 false
。
3.3.4.1 Upgrading to MySQL Connector/J 8.0 from Connector/J 5.1 提到了这次修改,此外在一些发布日志(例如 Changes in MySQL Connector/J 8.0.17 (2019-07-22, General Availability))中也可以看到 nullCatalogMeansCurrent
这个参数重新命名成了 nullDatabaseMeansCurrent
。
MySQL Connector/J Developer Guide 中提到这个参数的作用是 In ‘DatabaseMetaData’ methods that take a ‘catalog’ or ‘schema’ parameter, does the value “null” mean to use the current database?。
已经在配置中显示指定了数据源的情况下为什么要特别说明连接哪个数据库?通过调试看看发生了什么。
执行逻辑是在 org.flowable.common.engine.impl.db.ServiceSqlScriptBasedDbSchemaManager#schemaUpdate(java.lang.String)
方法中分道扬镳的:
// ...
if (isUpdateNeeded()) {
// nullDatabaseMeansCurrent=false
dbSchemaUpgrade(...
} else {
// nullDatabaseMeansCurrent=true
schemaCreate();
}
// ...
isUpdateNeeded()
通过 org.flowable.common.engine.impl.db.AbstractSqlScriptBasedDbSchemaManager#isTablePresent(java.lang.String)
判断给定表是否存在,判断依据是下方的查询结果集是否有匹配:
ResultSet tables = databaseMetaData.getTables(catalog, schema, tableName, JDBC_METADATA_TABLE_TYPES);
不过无论代码执行是否异常,catalog
和 schema
始终为 null
,看不出区别。继续调试,项目使用了 HikariCP,调用链如下:
com.zaxxer.hikari.pool.ProxyDatabaseMetaData#getTables
|-- com.mysql.cj.jdbc.DatabaseMetaData#getTables
后者使用 getDatabase
方法获取数据库的名称,用于查询表:
protected String getDatabase(String catalog, String schema) {
if (this.databaseTerm.getValue() == DatabaseTerm.SCHEMA) {
return schema == null && this.nullDatabaseMeansCurrent.getValue() ? this.database : schema;
}
return catalog == null && this.nullDatabaseMeansCurrent.getValue() ? this.database : catalog;
}
nullDatabaseMeansCurrent=true
时返回当前数据库,若 nullDatabaseMeansCurrent=false
,由于 catalog
和 schema
均为 null
,getTables
会搜索整个 MySQL 实例上的表。刚好这个测试 MySQL 实例上其他数据库中存在 Flowable 相关的表,删除之后项目恢复正常。
不过其他场景下就不能如此随便了,因此另一个方案是填上 catalog
/ schema
参数。
分析代码,在org.flowable.common.engine.impl.db.AbstractSqlScriptBasedDbSchemaManager#isTablePresent(java.lang.String)
中设置了 catalog
变量。
String catalog = dbSqlSession.getConnectionMetadataDefaultCatalog();
if (dbSqlSessionFactory.getDatabaseCatalog() != null && dbSqlSessionFactory.getDatabaseCatalog().length() > 0) {
catalog = dbSqlSessionFactory.getDatabaseCatalog();
}
由于这个 org.flowable.common.engine.impl.db.DbSqlSession
实例既没有在构造时为 connectionMetadataDefaultCatalog
赋值,也没有在后续调用 Setter 方法,因此 catalog
的值只能来自 org.flowable.common.engine.impl.db.DbSqlSessionFactory
实例的 databaseCatalog
属性。
org.flowable.common.engine.impl.AbstractEngineConfiguration#initDbSqlSessionFactory
中调用了这些属性的 Setter 方法,在 FlowableEngineConfigurationConfigurer
中补充配置,项目可以正常运行了。
@Configuration
public class FlowableEngineConfigurationConfigurer
implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
public void configure(SpringProcessEngineConfiguration processEngineConfiguration) {
processEngineConfiguration
// ...
.setDatabaseCatalog("dorian");
}
schema
同理,不过若要使用 setDatabaseSchema("dorian")
,还需在 JDBC URL 连接中添加 databaseTerm=SCHEMA
。
MySQL uses the term “schema” as a synonym of the term “database,” while Connector/J historically takes the JDBC term “catalog” as synonymous to “database”. This property sets for Connector/J which of the JDBC terms “catalog” and “schema” is used in an application to refer to a database. The property takes one of the two values “CATALOG” or “SCHEMA” and uses it to determine (1) which Connection methods can be used to set/get the current database (e.g. ‘setCatalog()’ or ‘setSchema()’?), (2) which arguments can be used within the various ‘DatabaseMetaData’ methods to filter results (e.g. the catalog or ‘schemaPattern’ argument of ‘getColumns()’?), and (3) which fields in the result sets returned by ‘DatabaseMetaData’ methods contain the database identification information (i.e., the ‘TABLE_CAT’ or ‘TABLE_SCHEM’ field in the result set returned by ‘getTables()’?).
If “databaseTerm=CATALOG”, ‘schemaPattern’ for searches are ignored and calls of schema methods (like ‘setSchema()’ or get ‘Schema()’) become no-ops, and vice versa.
顺便看了下 Relationship between catalog, schema, user, and database instance 提到的在 Postgres 等数据库中这些概念的区别。