1. 项目概述:当Flowable遇见达梦
最近在几个国产化替代的项目里,我被问得最多的问题之一就是:“Flowable工作流引擎,能跑在达梦数据库上吗?” 这确实是个非常实际且迫切的需求。Flowable作为一款优秀的开源BPMN流程引擎,在Java生态里应用广泛,但它的官方文档和社区讨论,默认的焦点往往集中在MySQL、PostgreSQL、Oracle这些主流数据库上。而达梦数据库作为国产数据库的领军者,在信创项目中出现的频率越来越高。把这两者结合起来,并不是简单地改个数据库连接串就能搞定的事情,里面涉及到驱动适配、SQL方言兼容、建表脚本处理等一系列需要动手解决的细节。
我花了些时间,在几个实际项目中完成了从零开始的Flowable适配达梦数据库的整合工作。这个过程踩过一些坑,也总结出了一套相对稳定可靠的方案。这篇文章,我就把这些实操经验、核心配置步骤以及避坑指南系统地梳理出来。无论你是刚开始接触Flowable和达梦整合的架构师,还是正在为项目国产化改造头疼的开发工程师,相信这篇内容都能给你提供一条清晰的路径,让你能快速、稳妥地在达梦数据库上部署和运行Flowable流程引擎。
2. 核心适配原理与前置分析
在动手改配置之前,我们必须先理解Flowable与数据库交互的核心机制,这样才能知道我们的适配工作重点在哪里,避免盲目操作。
2.1 Flowable的数据库抽象层
Flowable引擎本身并不直接操作特定数据库的JDBC接口,而是通过其内部的数据库抽象层来实现。这个抽象层主要做两件事:
- SQL方言(Dialect)管理:不同的数据库,SQL语法存在差异,比如分页查询(LIMIT vs ROWNUM)、自增主键获取(
SELECT LAST_INSERT_ID()vsSELECT @@IDENTITY)、时间函数等。Flowable为每种支持的数据库提供了一个Dialect实现类,用于生成符合特定数据库语法的SQL。 - 连接与事务管理:它封装了JDBC连接池的获取、事务的开启与提交回滚等底层操作,让引擎核心代码与具体的数据库驱动解耦。
因此,要让Flowable支持达梦,关键在于两点:一是确保有兼容的JDBC驱动,二是要让Flowable“认识”达梦,即提供或配置正确的SQL方言。
2.2 达梦数据库的兼容性定位
达梦数据库通常兼容Oracle或PostgreSQL的语法。这是一个非常重要的起点。在大多数情况下,尤其是较新版本的达梦(如DM8),其SQL语法、数据类型、序列机制等与Oracle的兼容性非常高。这意味着,我们很可能可以借用Flowable对Oracle的支持来“曲线救国”。Flowable内置了org.flowable.engine.impl.db.OracleDbSchemaManager和对应的方言处理器。我们的适配策略,很大程度上就是引导Flowable将达梦数据库当作一个“特殊的Oracle”来处理。
然而,“兼容”不等于“完全一致”。达梦有其自身的特性,比如驱动类名、URL格式、以及某些细微的语法或函数差异。这些地方就是我们需要特别关注和处理的“坑点”。直接使用Oracle的配置可能会在运行时遇到一些意想不到的错误,需要我们根据实际情况进行微调。
2.3 环境与依赖准备
在开始编码之前,需要准备好以下环境与组件:
- 达梦数据库:你需要一个正在运行的达梦数据库实例。可以从达梦官网下载开发版或试用版,安装在本地或服务器上。记住它的连接信息:IP、端口、服务名(或数据库名)、用户名、密码。
- JDBC驱动:这是连接的关键。务必使用与你的达梦数据库版本匹配的JDBC驱动Jar包(通常是
dm8-xxx-jdbc.jar或类似名称)。不要使用过旧或来源不明的驱动。 - Flowable依赖:在你的项目中引入Flowable Spring Boot Starter或其他Flowable核心模块。这里以Spring Boot项目为例,这是最常见的集成方式。
一个典型的Maven依赖配置如下(版本号请根据实际情况调整):
<dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-process</artifactId> <version>6.8.0</version> <!-- 建议使用较新稳定版 --> </dependency> <!-- 其他Spring Boot基础依赖省略 -->关键点:达梦的JDBC驱动Jar需要被项目引用。你可以将它安装到本地Maven仓库,或直接放入项目的lib目录并通过system路径依赖。更规范的做法是将其上传到公司的私有Nexus仓库。这里假设你已处理好驱动依赖。
3. 详细配置与整合步骤
接下来,我们进入实操环节。我将以Spring Boot项目为例,分步讲解如何配置。
3.1 数据源配置
这是最基础的一步,在application.yml或application.properties中配置数据源。这里有一个关键技巧:在url中显式指定com.dameng.DmDialect方言。虽然Flowable后续会用自己的方言,但这里指定可以帮助一些连接池或工具更好地初始化。
spring: datasource: url: jdbc:dm://localhost:5236/DAMENG?schema=你的模式名&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai # 注意:达梦默认端口是5236,DAMENG是默认服务名,请根据实际情况修改。 # `schema`参数非常重要,通常等于你的用户名,它指定了连接后默认的模式。 username: FLOWABLE_USER # 建议为Flowable创建专属用户 password: your_strong_password driver-class-name: dm.jdbc.driver.DmDriver # 达梦8驱动类名 hikari: connection-test-query: SELECT 1 FROM DUAL # 连接测试查询,使用达梦兼容的语法注意:达梦数据库的“模式(Schema)”概念与用户强关联。通常,一个用户名就对应一个同名的模式。确保连接URL中的
schema参数或连接属性设置正确,否则建表可能会建到错误的模式下。
3.2 Flowable引擎配置
我们需要通过Java配置类,告诉Flowable引擎使用针对达梦(或兼容的Oracle)的配置。创建一个配置类,例如FlowableDmConfig:
@Configuration public class FlowableDmConfig { @Bean public SpringProcessEngineConfiguration springProcessEngineConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager) { SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setDataSource(dataSource); config.setTransactionManager(transactionManager); config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); // 自动更新表结构,首次启动时使用 // !!!核心配置:指定数据库类型为Oracle config.setDatabaseType("oracle"); // 可选:如果你使用的达梦驱动版本较新,或者遇到特定错误,可能需要自定义方言 // config.setCustomMybatisMappers(...); 和 config.setCustomMybatisXMLMappers(...) 可用于深度定制 // 但对于大多数情况,设置为oracle并使用默认方言即可。 // 设置异步执行器(按需) config.setAsyncExecutorActivate(true); config.setAsyncExecutor().setCorePoolSize(10); return config; } }核心解释:config.setDatabaseType("oracle")这行代码至关重要。它直接指示Flowable内部使用针对Oracle数据库的SQL方言和建表脚本。因为达梦与Oracle高度兼容,这是最快捷有效的适配方式。
3.3 处理建表脚本与Liquibase
Flowable首次启动时,如果表不存在,会根据databaseType选择对应的SQL脚本来建表。当我们设置为oracle后,它会尝试执行org/flowable/db/create/flowable.*.oracle.create.sql系列脚本。
潜在问题:达梦虽然兼容Oracle,但某些SQL语句的细微差别可能导致执行失败。最常见的问题之一是表空间和存储子句。Oracle的建表脚本中可能包含TABLESPACE ... STORAGE ...这样的子句,而达梦可能不支持或语法不同。
解决方案:
- 方案A(推荐,简单):利用
setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)让Flowable自动执行。如果遇到因表空间语句导致的错误,可以尝试在达梦数据库上创建一个同名的表空间,或者更常见的做法是,修改达梦的初始化参数,使其兼容或忽略这些子句。有时,达梦驱动能自动忽略不认识的存储子句。 - 方案B(可控,复杂):手动处理建表。
- 从Flowable的Jar包(
flowable-engine-*.jar)中提取出flowable.*.oracle.create.sql文件。 - 使用文本编辑器批量删除或注释掉所有
TABLESPACE和STORAGE相关的子句。 - 在达梦数据库上,使用客户端工具(如达梦管理工具或DBeaver)连接,为Flowable创建专属用户(模式),然后在该用户下执行修改后的SQL脚本。
- 将Spring配置中的
databaseSchemaUpdate设置为false或DB_SCHEMA_UPDATE_FALSE,禁止自动建表。
- 从Flowable的Jar包(
关于Liquibase:Flowable内部使用Liquibase进行版本化的数据库迁移管理。当你设置databaseType为oracle后,Flowable会自动加载针对Oracle的Liquibase变更日志(changelog)。只要达梦能顺利执行这些Oracle格式的SQL,Liquibase就能正常工作。如果遇到问题,可能需要像处理建表脚本一样,对Liquibase的XML文件中的特定SQL进行定制,但这属于更高级的定制,一般场景下不需要。
3.4 用户与权限准备
在达梦中,需要提前创建一个用于Flowable的数据库用户(这同时会创建一个同名的模式)。使用达梦的管理工具或SQL命令行执行:
CREATE USER FLOWABLE_USER IDENTIFIED BY "your_strong_password"; GRANT RESOURCE, VTI TO FLOWABLE_USER; -- 授予基本资源权限和VTI(虚拟表接口)权限 -- 根据实际需要,可能还需要授予CREATE TABLE, CREATE SEQUENCE等具体权限,但RESOURCE角色通常已包含。确保你的应用连接使用的正是这个用户,这样所有Flowable表都会创建在该用户模式下。
4. 常见问题排查与实战技巧
在实际整合过程中,你几乎一定会遇到下面这些问题。我把它们和解决方案整理出来,你可以像查字典一样使用。
4.1 驱动类找不到或连接失败
- 问题:启动时报
ClassNotFoundException: dm.jdbc.driver.DmDriver或No suitable driver found for jdbc:dm://...。 - 排查:
- 确认驱动Jar包是否在项目的类路径(Classpath)中。检查
pom.xml或build.gradle依赖,以及打包后的WEB-INF/lib或可执行Jar的BOOT-INF/lib目录。 - 确认
driver-class-name拼写正确。达梦8通常是dm.jdbc.driver.DmDriver,更早版本可能是dm.jdbc.driver.DmdbDriver,请以官方文档为准。 - 检查数据库URL格式、主机、端口、服务名是否正确。达梦的URL格式是
jdbc:dm://host:port/DATABASE。
- 确认驱动Jar包是否在项目的类路径(Classpath)中。检查
4.2 建表时SQL语法错误
- 问题:启动时控制台打印大量SQL异常,提示
Syntax error near 'TABLESPACE'或Invalid SQL statement。 - 解决:这就是上文提到的表空间问题。
- 临时解决:在达梦数据库服务器上,尝试执行
CREATE TABLESPACE FLOWABLE DATAFILE 'flowable.dbf' SIZE 128;创建一个表空间(名字与建表脚本中指定的一致)。但这并非总是有效,因为脚本中的表空间名可能不同。 - 根本解决:采用方案B(手动处理建表脚本)。这是最干净的方法。手动执行清理后的SQL脚本,然后将配置中的
databaseSchemaUpdate设为false。
- 临时解决:在达梦数据库服务器上,尝试执行
4.3 序列(Sequence)相关问题
- 问题:流程实例或任务ID生成失败,错误可能与
SEQ_FLW_EVNT_LOG等序列有关。 - 分析:Flowable为许多表使用数据库序列来生成ID。Oracle和达梦都支持序列,但创建和查询序列的语法完全兼容吗?大多数情况下是兼容的。
- 解决:如果遇到序列错误,检查建表脚本中创建序列的语句是否被成功执行。可以到达梦数据库中查询
SELECT * FROM USER_SEQUENCES;看看Flowable的序列是否存在。如果不存在,需要手动从清理后的建表脚本中找到创建序列的语句并执行。
4.4 分页查询异常
- 问题:在执行流程实例查询、历史任务查询等分页操作时,报错。
- 分析:分页查询是SQL方言差异的重灾区。Oracle使用ROWNUM进行分页,而Flowable的Oracle方言会生成类似
WHERE ROWNUM <= ?的语句。达梦兼容这种写法。 - 解决:如果出现分页错误,很可能是因为复杂的查询嵌套导致ROWNUM位置不正确。这时可能需要自定义一个
Dialect。继承自OracleDialect,重写getLimitString或getLimitAfter等方法,调整分页SQL的生成逻辑。这是一个进阶话题,需要你对MyBatis和Flowable内部SQL生成有一定了解。
4.5 时间类型和函数差异
- 问题:流程定时器(Timer)不触发,或者历史记录中的时间字段异常。
- 分析:Flowable会使用
SYSDATE或CURRENT_TIMESTAMP这样的函数。达梦通常兼容SYSDATE。 - 解决:确保在查询或条件中使用时间函数时,达梦能够正确解析。如果遇到问题,可以在自定义的
Dialect中重写相关的时间函数映射。
5. 进阶:自定义方言与深度适配
对于绝大多数应用场景,通过setDatabaseType("oracle")已经足够。但如果你的项目要求极高,或者遇到了无法通过上述方法解决的兼容性问题,就需要进行深度适配——自定义SQL方言。
5.1 创建自定义达梦方言类
public class DmDialect extends OracleDialect { // 继承自Oracle方言 public DmDialect() { super(); // 继承大部分Oracle的方言行为 // 可以在这里覆盖一些属性 // 例如,如果达梦的日期函数名不同,可以覆盖: // this.databaseSpecificLimitBefore = "..."; } @Override public String getLimitString(String sql, int offset, int limit) { // 如果需要调整达梦特有的分页语法(如DM8新的LIMIT/OFFSET),可以在这里重写 // 默认继承Oracle的ROWNUM方式,通常够用。 return super.getLimitString(sql, offset, limit); } // 重写其他需要适配的方法,例如时间函数、批量插入语句等 // @Override // public String getCurrentDateTimeFunction() { // return "SYSDATE"; // 达梦也支持SYSDATE // } }5.2 在配置中启用自定义方言
修改之前的配置类:
@Bean public SpringProcessEngineConfiguration springProcessEngineConfiguration(DataSource dataSource, PlatformTransactionManager transactionManager) { SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration(); config.setDataSource(dataSource); config.setTransactionManager(transactionManager); config.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE); // 不再设置databaseType为oracle,而是直接注入自定义的方言实例 // config.setDatabaseType("oracle"); // 注释掉这行 config.setCustomSqlSessionFactory(new DefaultSqlSessionFactory(config)); // 通常需要这个 // 关键:设置自定义方言 config.setDatabaseType("dm"); // 可以定义一个自定义的类型名 // 需要将自定义方言注册到Flowable的方言工厂中,这通常通过扩展配置实现 // 更直接的方式是,确保你的DmDialect在类路径下,并在flowable-default.properties中配置 // 但更简单的方式是,在初始化后手动设置: // ((SessionFactory) config.getSessionFactories().get(DbSqlSession.class)).getSessionType().registerDialect("dm", new DmDialect()); // 上面的方法较为底层,对于Spring Boot,更优雅的方式是实现一个ProcessEngineConfigurationConfigurer Bean }实际上,在Spring Boot环境中,更简洁的方式是使用ProcessEngineConfigurationConfigurer回调接口:
@Configuration public class FlowableDmConfig { @Bean public ProcessEngineConfigurationConfigurer dmProcessEngineConfigurationConfigurer() { return processEngineConfiguration -> { // 强制使用Oracle方言,或者你自定义的方言 processEngineConfiguration.setDatabaseType("oracle"); // 如果你创建了DmDialect,并且想用,可能需要更复杂的注册,这里用Oracle是最快路径 }; } }实操心得:除非迫不得已,否则不要轻易走上完全自定义方言的道路。优先尝试使用oracle类型,并配合手动清理建表脚本的方案。自定义方言需要对Flowable的SQL生成机制有深入了解,维护成本较高。我个人的经验是,90%的达梦适配问题,通过“Oracle类型 + 手动执行无表空间语句的建表SQL”就能解决。
6. 测试与验证
完成配置后,必须进行全面的测试。
- 启动测试:启动Spring Boot应用。观察控制台日志,应该能看到Flowable初始化成功,并打印出类似
“Flowable ProcessEngine default built”的日志,而没有大量的SQL错误。 - 表结构验证:连接到达梦数据库,查看为你配置的用户模式下,是否生成了大量以
ACT_开头的表(如ACT_RE_PROCDEF,ACT_RU_TASK等)。如果表都存在,说明建表成功。 - API基础测试:
- 编写一个单元测试或创建一个简单的Controller,注入
RepositoryService,部署一个最简单的BPMN流程图(比如只有一个开始事件和一个结束事件)。 - 注入
RuntimeService,启动一个流程实例。 - 注入
TaskService,查询并完成任务(如果流程中有用户任务)。 - 注入
HistoryService,查询历史流程实例。 确保这些基本操作都能成功执行,并且数据能正确写入和读出达梦数据库。
- 编写一个单元测试或创建一个简单的Controller,注入
- 功能点测试:根据你的业务需求,测试网关(排他、并行)、定时器、调用活动(子流程)、异步执行器等高级功能。特别是涉及复杂查询和事务的功能。
在整个测试过程中,密切观察达梦数据库的日志或监控,看是否有异常SQL或性能瓶颈。使用DBeaver或达梦自带的管理工具,可以方便地查看和验证数据。
最后,我想再强调一个很容易忽略的点:版本匹配。Flowable的不同小版本(如6.7.x, 6.8.x)可能在SQL脚本或内部实现上有细微调整。达梦数据库的不同版本(如DM7, DM8)也有差异。务必记录下你成功搭配的版本组合(例如:Flowable 6.8.0 + 达梦DM8 1-2-18-2024.07.xxx + JDBC Driver 8.x.x),这能为以后的升级和维护提供重要参考。整合过程中,耐心和细致的日志分析是你最好的工具,遇到报错不要慌,根据错误信息定位到具体的SQL语句,然后思考达梦与Oracle的语法差异,问题总能一步步解决。