首页 > 数据库 >flowable适配人大金仓Kingbase数据库

flowable适配人大金仓Kingbase数据库

时间:2024-01-10 17:26:26浏览次数:43  
标签:金仓 DB2 database flowable 适配 databaseTypeMappings connection setProperty db2

背景

因为国产化的需求,需要把现有项目的数据库改成人大金仓,适配某个项目的时候因为使用了没适配Kingbase的flowable,导致无法启动。
原本使用的是Oracle数据库,kingbase兼容Oracle数据库,可以直接当成Oracle来使用。

错误1: couldn't deduct database type from database product name 'KingbaseES'

image

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());
    }
}

分析原因

断点后发现在JdbcExecutorpublic Object execute(CallableStatementCallback action, List<SqlVisitor> sqlVisitors) throws DatabaseException方法执行了一条sqlcall current_schema,不支持这种语法就报错了。

image

通过调用堆栈发现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实现的。

继续通过调用堆栈向上查找,发现在LiquibaseBasedSchemaManagerschemaUpdate()方法上database消失了,说怕databsecreateLiquibaseInstance()方法上被确定下来。

image

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方法注册。

image

注册方法register虽然是public,但是我们代码中获取不到实例,所以考虑添加支持Kingbase的Database并让它被扫描到。

findClasses()方法实际是调用了findClassesImpl()方法,方法中使用了一个packagesToScan,应该是用来限制扫描包名范围的。

image

setResourceAccessor()方法中会初始化packagesToScan, 先读取系统属性liquibase.scan.packages,否则读取META-INF/MANIFEST.MF文件,再不然就使用默认的包名。

image

image

因此只需要把自己实现的Database丢到默认的包名下就可以被扫面描到。

标签:金仓,DB2,database,flowable,适配,databaseTypeMappings,connection,setProperty,db2
From: https://www.cnblogs.com/montaro/p/17956719

相关文章

  • 适配 iphone 底部刘海
    在需要适配的页面,元素增加样式。特有的变量safe-area-inset-bottom\safe-area-inset-bottom建议这种通用性设置,提供一个基础公共组件page.通过定义插槽nav\header\main\footer插入内容。这是一些公共的样式就不用每个页面去设置。.footer{padding-bottom:0;......
  • 再次拓宽信创生态版图,思迈特软件与统信软件完成产品兼容适配认证
    近日,思迈特软件与统信软件科技有限公司(简称“统信软件”)完成产品兼容性适配互认证,加速国产信创生态化建设进程。本次测试由商业智能与数据分析软件(简称:SmartbiInsightV11)产品与统信服务器操作系统V20 进行适配认证。测试结果显示SmartbiInsightV11在统信服务器操作系统V20上整......
  • 软件体系结构与设计模式之适配器模式
    一.单选题(共4题,8分)(单选题,2分)当想使用一个已经存在的类,但其接口不符合需求时,可以采用()设计模式将该类的接口转换成我们希望的接口。A.命令(Command)B.适配器(Adapter)C.装饰(Decorator)D.享元(Flyweight)我的答案:B:适配器(Adapter);正确答案:B:适配......
  • #星计划#【坚果派】JS开源库适配OpenHarmony系列——第一期实操
    (目录)JS开源库适配OpenHarmony系列第一期实操1.为什么适配JS开源库由于OpenHarmony应用是基于ArkTS开发,而ArkTS是在保持TypeScript(简称TS)基础语法风格的基础上,对TS的动态类型特性施加更严格的约束,引入静态类型。因此在开发OpenHarmony三方库时,建议首选在成熟的JS/TS开源三方......
  • openKylin 1.0 成功适配 Inte l 最新 x86 平台 Raptor Lake 系列
    2024大语言模型技术报告.pdf 2024年1月2日,openKylin1.0成功适配Intel(英特尔)最新x86平台RaptorLake系列,与国际OSV同步。 Intel中国公司在2023年11月加入openKylin社区之后,成立了IntelSIG并与openKylin社区开展合作,经过IntelSIG团队成员的努力,目前op......
  • Rem部署适配
    参考vant官网:https://vant-contrib.gitee.io/vant/v3/#/zh-CN/advanced-usage两个插件:1.postcss-pxtorem是一款PostCSS插件,用于将px单位转化为rem单位2.lib-flexible用于设置rem基准值3.示例配置//postcss.config.jsmodule.exports={plugins:{'p......
  • 程序员必知!适配器模式的实战应用与案例分析
    适配器模式是一种结构型设计模式,它允许不同接口的对象协同工作,它通过将一个类的接口转换成客户希望的另外一个接口,使得不兼容的类可以一起工作。适配器模式提高了类的复用性、系统的灵活性和可扩展性,并降低了系统间的耦合度,在实际应用中,例如电源适配器和数据转换器,以及编程中封装......
  • TI 专访 Merlin Protocol:构建在比特币网络上的资产适配协议
    近期BTC生态上铭文的热度和流量为市场带起了一波小的高潮。越来越多的用户开始关注BRC-20赛道。但作为铭文,BRC-20稀缺的应用场景和价值也一直为人们所诟病。MerlinProtocol,一个构建在比特币网络上的资产适配协议,提出了自己独特的解决方案。今天,我们邀请到了MerlinProtocol......
  • windows 网络适配器
     添加虚拟网卡:            https://blog.51cto.com/elasticsearch/5488949如何区分虚拟网卡和物理网卡:           参考:https://blog.csdn.net/EDDJH_31/article/details/82694205 ......
  • 设计模式 之适配器模式
    适配器模式(适配器模式)定义==适配器模式==将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。适配器模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者。这种做法还有额外的优点,那就是被适配者的任何子类都可以搭配着适配器......