背景
因为国产化的需求,需要把现有项目的数据库改成人大金仓,适配某个项目的时候因为使用了没适配Kingbase的flowable,导致无法启动。
原本使用的是Oracle数据库,kingbase兼容Oracle数据库,可以直接当成Oracle来使用。
错误1: couldn't deduct database type from database product name 'KingbaseES'
Caused by: org.flowable.common.engine.api.FlowableException: couldn't deduct database type from database product name 'KingbaseES'
at org.flowable.common.engine.impl.AbstractEngineConfiguration.initDatabaseType(AbstractEngineConfiguration.java:486)
at org.flowable.common.engine.impl.AbstractEngineConfiguration.initDataSource(AbstractEngineConfiguration.java:456)
at org.flowable.app.engine.AppEngineConfiguration.init(AppEngineConfiguration.java:198)
at org.flowable.app.engine.AppEngineConfiguration.buildAppEngine(AppEngineConfiguration.java:183)
at org.flowable.app.spring.SpringAppEngineConfiguration.buildAppEngine(SpringAppEngineConfiguration.java:63)
at org.flowable.app.spring.AppEngineFactoryBean.getObject(AppEngineFactoryBean.java:59)
at org.flowable.app.spring.AppEngineFactoryBean.getObject(AppEngineFactoryBean.java:31)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:169)
... 213 common frames omitted
解决方法
先说解决方法,通过AOP直接指定databaseType为oracle
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.flowable.app.engine.AppEngineConfiguration;
import org.springframework.stereotype.Component;
@Aspect
@Component
@RequiredArgsConstructor
public class KingbaseSupport {
private final AppEngineConfiguration appEngineConfiguration;
@Pointcut("execution(* org.flowable.app.engine.AppEngineConfiguration.buildAppEngine())")
public void access() {
}
@Before("access()")
public void before() {
appEngineConfiguration.setDatabaseType("oracle");
}
}
分析原因
查看报错的堆栈信息,报错是在AbstractEngineConfiguration.initDatabaseType
方法上, 代码如下(省略部分代码):
public void initDatabaseType() {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
String databaseProductName = databaseMetaData.getDatabaseProductName();
this.logger.debug("database product name: '{}'", databaseProductName);
this.databaseType = this.databaseTypeMappings.getProperty(databaseProductName);
if (this.databaseType == null) {
throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'");
}
this.logger.debug("using database type: {}", this.databaseType);
} catch (SQLException var56) {
this.logger.error("Exception while initializing Database connection", var56);
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException var49) {
this.logger.error("Exception while closing the Database connection", var49);
}
}
if ("mssql".equals(this.databaseType)) {
this.maxNrOfStatementsInBulkInsert = this.DEFAULT_MAX_NR_OF_STATEMENTS_BULK_INSERT_SQL_SERVER;
}
}
调试后发现,databaseProductName
的值是KingbaseES, 从databaseTypeMappings
中没有获取到名为KingbaseES的Property。
那么我们只要添加KingbaseES进去就可以了
继续查看代码,databaseTypeMappings
的值来自AbstractEngineConfiguration.getDefaultDatabaseTypeMappings()
方法
protected Properties databaseTypeMappings = getDefaultDatabaseTypeMappings();
public static Properties getDefaultDatabaseTypeMappings() {
Properties databaseTypeMappings = new Properties();
databaseTypeMappings.setProperty("H2", "h2");
databaseTypeMappings.setProperty("HSQL Database Engine", "hsql");
databaseTypeMappings.setProperty("MySQL", "mysql");
databaseTypeMappings.setProperty("MariaDB", "mysql");
databaseTypeMappings.setProperty("Oracle", "oracle");
databaseTypeMappings.setProperty("PostgreSQL", "postgres");
databaseTypeMappings.setProperty("Microsoft SQL Server", "mssql");
databaseTypeMappings.setProperty("db2", "db2");
databaseTypeMappings.setProperty("DB2", "db2");
databaseTypeMappings.setProperty("DB2/NT", "db2");
databaseTypeMappings.setProperty("DB2/NT64", "db2");
databaseTypeMappings.setProperty("DB2 UDP", "db2");
databaseTypeMappings.setProperty("DB2/LINUX", "db2");
databaseTypeMappings.setProperty("DB2/LINUX390", "db2");
databaseTypeMappings.setProperty("DB2/LINUXX8664", "db2");
databaseTypeMappings.setProperty("DB2/LINUXZ64", "db2");
databaseTypeMappings.setProperty("DB2/LINUXPPC64", "db2");
databaseTypeMappings.setProperty("DB2/400 SQL", "db2");
databaseTypeMappings.setProperty("DB2/6000", "db2");
databaseTypeMappings.setProperty("DB2 UDB iSeries", "db2");
databaseTypeMappings.setProperty("DB2/AIX64", "db2");
databaseTypeMappings.setProperty("DB2/HPUX", "db2");
databaseTypeMappings.setProperty("DB2/HP64", "db2");
databaseTypeMappings.setProperty("DB2/SUN", "db2");
databaseTypeMappings.setProperty("DB2/SUN64", "db2");
databaseTypeMappings.setProperty("DB2/PTX", "db2");
databaseTypeMappings.setProperty("DB2/2", "db2");
databaseTypeMappings.setProperty("DB2 UDB AS400", "db2");
databaseTypeMappings.setProperty("CockroachDB", "cockroachdb");
return databaseTypeMappings;
}
这里完全写死了,没办法修改,同时也没有找到添加的方法。
通过不停的下断点, 调用链是:
AppEngineConfiguration.buildAppEngine()
↓
AppEngineConfiguration.init()
↓
AbstractEngineConfiguration.initDataSource()
↓
AbstractEngineConfiguration.initDatabaseType()
在initDataSource
方法中,有判断databaseType
是否为空,如果为则会调用initDatabaseType()
方法:
if (this.databaseType == null) {
this.initDatabaseType();
}
因此提前设置好databaseType
就可以了。
错误2: com.kingbase8.util.KSQLException: 错误: 语法错误 在输入的末尾
解决方法
添加一个包名liquibase.database
,在里面添加一个KingbaseDatabase类:
package liquibase.database;
import liquibase.database.core.OracleDatabase;
import liquibase.exception.DatabaseException;
public class KingbaseDatabase extends OracleDatabase {
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return super.isCorrectDatabaseImplementation(conn) || "KingbaseES".equals(conn.getDatabaseProductName());
}
}
分析原因
断点后发现在JdbcExecutor
的public Object execute(CallableStatementCallback action, List<SqlVisitor> sqlVisitors) throws DatabaseException
方法执行了一条sqlcall current_schema
,不支持这种语法就报错了。
通过调用堆栈发现sql语句在这个方法中获取AbstractJdbcDatabase.getConnectionSchemaName
:
protected String getConnectionSchemaName() {
if (this.connection == null) {
return null;
} else if (this.connection instanceof OfflineConnection) {
return ((OfflineConnection)this.connection).getSchema();
} else {
try {
SqlStatement currentSchemaStatement = this.getConnectionSchemaNameCallStatement();
return (String)ExecutorService.getInstance().getExecutor(this).queryForObject(currentSchemaStatement, String.class);
} catch (Exception var2) {
LogService.getLog(this.getClass()).info(LogType.LOG, "Error getting default schema", var2);
return null;
}
}
}
关键点聚焦在getConnectionSchemaNameCallStatement()
这个方法上, 默认返回的就是call current_schema
这条SQL。
protected SqlStatement getConnectionSchemaNameCallStatement() {
return new RawCallStatement("call current_schema");
}
getConnectionSchemaName()
方法只有在getDefaultSchemaName()
方法中被调用,而且getDefaultSchemaName()
方法是实现Database
接口的方法。
public abstract class AbstractJdbcDatabase implements Database {
// 省略部分代码
public String getDefaultSchemaName() {
if (!this.supportsSchemas()) {
return this.getDefaultCatalogName();
} else {
if (this.defaultSchemaName == null && this.connection != null) {
this.defaultSchemaName = this.getConnectionSchemaName();
}
return this.defaultSchemaName;
}
}
}
也就是说不同类型的数据库应该会有不同类型的Database
实现,接下来要找到是如何确定Database
实现的。
继续通过调用堆栈向上查找,发现在LiquibaseBasedSchemaManager
的schemaUpdate()
方法上database
消失了,说怕databse
在createLiquibaseInstance()
方法上被确定下来。
protected Liquibase createLiquibaseInstance(LiquibaseDatabaseConfiguration databaseConfiguration) throws SQLException {
Connection jdbcConnection = null;
boolean closeConnection = false;
try {
CommandContext commandContext = Context.getCommandContext();
if (commandContext == null) {
jdbcConnection = databaseConfiguration.getDataSource().getConnection();
closeConnection = true;
} else {
jdbcConnection = ((DbSqlSession)commandContext.getSession(DbSqlSession.class)).getSqlSession().getConnection();
}
if (!jdbcConnection.getAutoCommit()) {
jdbcConnection.commit();
}
DatabaseConnection connection = new JdbcConnection(jdbcConnection);
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
database.setDatabaseChangeLogTableName(this.changeLogPrefix + database.getDatabaseChangeLogTableName());
database.setDatabaseChangeLogLockTableName(this.changeLogPrefix + database.getDatabaseChangeLogLockTableName());
String databaseSchema = databaseConfiguration.getDatabaseSchema();
if (StringUtils.isNotEmpty(databaseSchema)) {
database.setDefaultSchemaName(databaseSchema);
database.setLiquibaseSchemaName(databaseSchema);
}
String databaseCatalog = databaseConfiguration.getDatabaseCatalog();
if (StringUtils.isNotEmpty(databaseCatalog)) {
database.setDefaultCatalogName(databaseCatalog);
database.setLiquibaseCatalogName(databaseCatalog);
}
return new Liquibase(this.changeLogFile, new ClassLoaderResourceAccessor(), database);
} catch (Exception var9) {
if (jdbcConnection != null && closeConnection) {
jdbcConnection.close();
}
throw new FlowableException("Error creating " + this.context + " liquibase instance", var9);
}
}
关键在这一行:
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
findCorrectDatabaseImplementation方法
从implementedDatabases
变量中获取对应的database
Database implementedDatabase = (Database)var3.next();
if (connection instanceof OfflineConnection) {
if (((OfflineConnection)connection).isCorrectDatabaseImplementation(implementedDatabase)) {
foundDatabases.add(implementedDatabase);
}
} else if (implementedDatabase.isCorrectDatabaseImplementation(connection)) {
foundDatabases.add(implementedDatabase);
}
那implementedDatabases
从哪里来?
原来在DatabaseFactory
的构造方法中进行了初始化, 搜索所有实现了Database
的类并调用register
方法注册。
注册方法register
虽然是public,但是我们代码中获取不到实例,所以考虑添加支持Kingbase的Database并让它被扫描到。
findClasses()
方法实际是调用了findClassesImpl()
方法,方法中使用了一个packagesToScan
,应该是用来限制扫描包名范围的。
在setResourceAccessor()
方法中会初始化packagesToScan
, 先读取系统属性liquibase.scan.packages
,否则读取META-INF/MANIFEST.MF
文件,再不然就使用默认的包名。
因此只需要把自己实现的Database丢到默认的包名下就可以被扫面描到。
标签:金仓,DB2,database,flowable,适配,databaseTypeMappings,connection,setProperty,db2 From: https://www.cnblogs.com/montaro/p/17956719