flowable流程引擎——流程版本迁移
在 Flowable 里,taskService 本身只提供与 任务(Task) 相关的操作(查询、完成、指派等),它不会直接告诉这个任务对应的 BPMN 模型版本。
要知道任务属于哪个版本的流程定义,需要顺着 Task → ProcessInstance → ProcessDefinition → Deployment → Version 这条链路来查。
具体步骤:
- 通过任务 ID 获取任务
Task task = taskService.createTaskQuery()
.taskId(taskId)
.singleResult();
- 从任务中拿到流程实例 ID
String processInstanceId = task.getProcessInstanceId();
- 查询流程实例
ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
- 获取流程定义 ID
String processDefinitionId = processInstance.getProcessDefinitionId();
- 查询流程定义,拿到版本号
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
Integer version = processDefinition.getVersion();
String deploymentId = processDefinition.getDeploymentId();
String resourceName = processDefinition.getResourceName(); // 通常是 bpmn 文件名
这样就能知道:
- 当前任务对应的 流程定义 key(同一个流程的逻辑标识)。
- 当前实例运行的 流程定义版本号(version,从 1 开始自增)。
- 部署时的 bpmn 文件名 或者 XML 资源。
taskService 本身不知道流程版本,但通过 task.getProcessInstanceId() → runtimeService → repositoryService,就能追溯到任务对应的 流程定义版本 和 BPMN 文件。
当我们新发布了一个错误的版本,但业务单据已经流转,并且无法向下执行时,如何处理
在 Flowable版本的使用规则是这样的:
-
流程实例启动时,会绑定一个确定的 流程定义版本。
- 例如:部署了 v1,然后启动一个实例,这个实例就永远跑在 v1 上。
- 即使之后又部署了 v2,这个正在跑的实例不会自动切换到 v2。
-
新建的流程实例
-
默认会使用 同一个 key 下的最新版本(比如 key=“myProcess”,部署了 v1 和 v2,那么新启动的实例会走 v2)。
-
除非在启动时显式指定
processDefinitionId或processDefinitionKey + version。如下:
方式一:用
processDefinitionId启动String processDefinitionId = "myProcess:2:7504"; // 格式: {KEY}:{VERSION}:{DB_ID} ProcessInstance processInstance = runtimeService .createProcessInstanceBuilder() .processDefinitionId(processDefinitionId) .start();这样会精确绑定到某个特定的流程定义版本。
方式二:用
processDefinitionKey+ 指定版本RuntimeService本身没有直接提供“指定版本号”的 API,
你需要先通过RepositoryService查询某个 key 的指定版本,再启动。// 查询指定 key + version 的流程定义 ProcessDefinition definition = repositoryService .createProcessDefinitionQuery() .processDefinitionKey("myProcess") .processDefinitionVersion(2) // 指定版本号 .singleResult(); // 用该流程定义启动实例 ProcessInstance processInstance = runtimeService .createProcessInstanceBuilder() .processDefinitionId(definition.getId()) .start();
-
-
正在运行的流程实例
- 仍然按照它启动时绑定的那个版本执行,不会被迁移到新版本。
- 所以说的情况:
- 部署了 v1 → 启动了一个流程 → 正在进行中。
- 发现问题,部署了 v2。
- 那个“走到一半”的流程 继续在 v1 上跑完。
- 新启动的流程 会走 v2。
此时如果想让 正在运行的实例迁移到新版本,Flowable 提供了 流程实例迁移 API:
ProcessInstanceMigrationBuilder builder =
processMigrationService.createProcessInstanceMigrationBuilder()
.migrateToProcessDefinition("newProcessDefinitionId"); // v2 的流程定义ID,在质量管理系统中,这个功能被放到了“设置主版本”按钮中,这样方便拿到最新的流程定义ID。
builder.migrate(processInstanceId);
这样可以把 已有实例 从 v1 迁移到 v2,但要注意:
- 迁移要考虑节点的兼容性(v1 和 v2 节点结构要能对应得上,不然会失败,后面会详细解释)。
- 一般情况不用走流程迁移(这里大多是我个人理解),对流程来说,任务的节点发生了改变或者处理人发生了改变,属于线下业务本身发生了改变,重要流程不会随便迁移,以往遇到的更多的情况是 新实例走新版本,旧实例跑完旧版本。这里我们为了防止在流程配置出错时,单据已经流转到某个节点,且业务无法向下进行,设置了当新版本发布且被设置为主版本时迁移一周内的流程实例到最新的版本上。
发布版本的时候不知道流程实例的id
在 发布新版本(部署 BPMN v2) 的时候,Flowable 不会自动知道哪些实例要迁移,所以必须先找到正在运行的实例,才能拿到 processInstanceId 来调用 processMigrationService。
获取正在运行的流程实例 ID 的方法
用 runtimeService 查询:
List<ProcessInstance> instances = runtimeService
.createProcessInstanceQuery()
.processDefinitionKey("yourProcessKey") // 流程定义的 key,不是 id
.list();
processDefinitionKey("yourProcessKey"):指定流程定义的 key(相同流程不同版本的 key 一样)。pi.getId():就是要的processInstanceId。pi.getProcessDefinitionId():能看到当前实例跑在哪个版本(v1 或 v2)。
整体流程(举例)
- 部署 v1 → 启动实例 A(绑定 v1)。
- 部署 v2 → Flowable 不会自动迁移,实例 A 仍在 v1 上跑。
- 查询所有 v1 的实例:
List<ProcessInstance> oldInstances = runtimeService
.createProcessInstanceQuery()
.processDefinitionId("oldProcessDefinitionId") // v1 的 ID
.list();
- 迁移到 v2:
ProcessDefinition newDefinition = repositoryService.createProcessDefinitionQuery()
.processDefinitionKey("yourProcessKey")
.latestVersion()
.singleResult();
for (ProcessInstance pi : oldInstances) {
processMigrationService.createProcessInstanceMigrationBuilder()
.migrateToProcessDefinition(newDefinition.getId())
.migrate(pi.getId());
}
processInstanceId是 正在运行的实例才有的。- 发布新版本(v2)时,得用
runtimeService去查所有旧版本实例,把它们的 ID 拿出来,再调用迁移 API。 - 没有办法在“发布版本”的瞬间自动知道实例,因为部署只是定义流程,不涉及运行中的数据。
可能遇到的错误
org.flowable.common.engine.api.FlowableException:
Migration Activity mapping missing for activity definition Id:'NCR-stage-80' or its MI Parent
意思是:
要迁移的流程实例,在运行到 NCR-stage-80 这个节点(或者它的多实例父节点 MI Parent)时,Flowable 在 v1 → v2 的迁移中找不到对应的节点映射。
为什么会报这个错?
Flowable 的迁移机制是 按节点 ID 对应 的:
- v1 和 v2 流程定义里,节点 ID 必须一致,才能直接迁移。
- 如果在 v2 里 删除/修改了 ID(哪怕只是改了
<userTask id="NCR-stage-80">→<userTask id="NCR-stage-82">),那迁移时就会报错。 - 如果节点有 多实例(multi-instance, MI),那更严格,必须配置映射。
解决方案
显式配置节点映射
如果 v2 确实改了 id,比如 v1 里是 NCR-stage-80,v2 里改成了 NCR-stage-82,就要在迁移时加上 mapping:
processMigrationService.createProcessInstanceMigrationBuilder()
.migrateToProcessDefinition(newDefinition.getId())
.addActivityMigrationMapping(
ActivityMigrationMapping.createMapping("NCR-stage-80", "NCR-stage-82")
)
.migrate(processInstanceId);
如果是多实例父节点改了,也需要显式配置父节点的 mapping。
建议的迁移步骤
-
查询旧流程定义和新流程定义
ProcessDefinition oldDef = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("yourKey") .processDefinitionVersion(1) // v1 .singleResult(); ProcessDefinition newDef = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("yourKey") .latestVersion() // v2 .singleResult(); -
迁移时加映射(如果 ID 改了)
processMigrationService.createProcessInstanceMigrationBuilder() .migrateToProcessDefinition(newDef.getId()) .addActivityMigrationMapping(ActivityMigrationMapping.createMapping("oldTaskId", "newTaskId")) .migrate(processInstanceId);
经验总结:
- 不建议频繁改节点 ID,会导致版本迁移还有业务代码的改动特别麻烦。
bpmn在哪张表里存着
在 Flowable 里,BPMN XML 文件本身是存储在 部署表里的,而不是流程实例表。具体如下:
关键表
| 表名 | 描述 |
|---|---|
ACT_RE_PROCDEF | 流程定义表,记录每个流程定义的信息(ID、key、版本、部署ID 等) |
ACT_RE_DEPLOYMENT | 流程部署表,记录每次部署的信息(部署ID、时间、名称) |
ACT_GE_BYTEARRAY | 流程资源表,存储 BPMN XML 和其他资源(图片、表单等),内容是二进制 |
ACT_RE_MODEL | (可选)模型表,如果在 Flowable Modeler 里建流程,会存模型数据 |
迁移更改的是哪些表的数据
核心受影响表
① 运行实例表
| 表名 | 描述 | 变化内容 |
|---|---|---|
ACT_RU_EXECUTION | 正在运行的执行对象 | PROC_DEF_ID_ 更新为新版本的 processDefinitionId,影响流程实例和子执行绑定 |
ACT_RU_TASK | 正在运行的任务 | 任务 TASK_DEF_KEY_ 或 EXECUTION_ID_ 根据迁移映射更新到新版本对应节点 |
ACT_RU_IDENTITYLINK | 任务候选人/参与人 | 如果任务迁移到新节点,对应的候选人/assignee 不变,但 TASK_ID_ 会关联迁移后的任务 |
② 历史表(不会改动,只影响查询路径)
| 表名 | 描述 | 变化内容 |
|---|---|---|
ACT_HI_TASKINST | 历史任务 | 迁移不会修改原历史任务,只是后续生成的任务会绑定新节点 |
ACT_HI_PROCINST | 历史流程实例 | 不修改老实例历史,只影响 runtime 执行绑定的新版本流程定义 |
ACT_HI_ACTINST | 历史活动实例 | 已完成的活动不会改变,迁移只影响正在执行的活动 |
数据变化概览
| 类型 | 改变对象 | 描述 |
|---|---|---|
| 流程实例绑定 | ACT_RU_EXECUTION.PROC_DEF_ID_ | 老实例指向新版本流程定义 |
| 执行节点 | ACT_RU_EXECUTION.ACT_ID_ | 如果节点 ID 对应新版本不同,迁移会更新 ACT_ID_ |
| 正在运行任务 | ACT_RU_TASK.TASK_DEF_KEY_ / EXECUTION_ID_ | 迁移后任务指向新节点 |
| 候选人 | ACT_RU_IDENTITYLINK.TASK_ID_ | 任务迁移到新节点后,关联 TASK_ID 会更新 |
注意事项
- 已完成的历史任务/历史活动不会改动。
- 迁移失败(节点 ID 不匹配)会抛出异常,数据库不会改动。
- 多实例任务(MI)迁移时,父节点
ACT_RU_EXECUTION也会更新对应的PROC_DEF_ID_和ACT_ID_。
- 迁移主要改动的是运行表
ACT_RU_EXECUTION和ACT_RU_TASK,让实例和任务绑定到新版本。 - 历史表不修改,原始记录保留。
- 资源表(BPMN XML)不修改,部署内容保持不变。
迁移为什么不更新ACT_RU_TASK的PROC_DEF_ID_而只更新ACT_RU_EXECUTION的PROC_DEF_ID_
这个现象在 Flowable 是 设计使然,原因和机制如下:
表结构回顾
ACT_RU_EXECUTION- 代表流程实例及其执行对象(Execution)。
PROC_DEF_ID_记录实例绑定的流程定义。- 迁移会更新这里的
PROC_DEF_ID_,保证实例整体指向新版本流程定义。
ACT_RU_TASK- 代表 运行中的任务(Task)。
PROC_DEF_ID_一般保持和创建任务时的流程定义一致。- 迁移不会自动改这里的
PROC_DEF_ID_,因为任务本身是绑定到执行对象(Execution)上的。
为什么 Task 的 PROC_DEF_ID_ 不变
- 任务关联的是 Execution
- Task 表里有
EXECUTION_ID_字段,Flowable 运行时 通过 Execution 找流程定义。 TASK.PROC_DEF_ID_只是冗余信息,用于查询/显示,不影响流程执行。
- Task 表里有
- 迁移安全考虑
- 任务节点可能与新版本不匹配(ID改动、多实例等)。
- 自动改
TASK.PROC_DEF_ID_可能导致任务逻辑异常。 - Flowable 只更新 Execution 的
PROC_DEF_ID_,保证实例运行时引用新流程定义。 - 任务的节点映射由
ActivityMigrationMapping决定,而不是直接改字段。
- 历史兼容性
- 已创建的任务保留原
PROC_DEF_ID_,便于追踪、查询历史或报表。
- 已创建的任务保留原
| 表 | PROC_DEF_ID_ 更新情况 | 说明 |
|---|---|---|
| ACT_RU_EXECUTION | 更新 | 流程实例迁移后指向新版本流程定义 |
| ACT_RU_TASK | 不更新 | 任务节点绑定 Execution,由 ActivityMigrationMapping 控制节点迁移;PROC_DEF_ID_ 只是冗余显示,不影响运行 |
所以这是 Flowable 的设计逻辑,保证迁移安全而不破坏任务执行。
ACT_RU_EXECUTION 表的含义
对于ACT_RU_TASK不做过多解释,它存放的是正在运行的任务实例,这里说一下ACT_RU_EXECUTION表。 在 Flowable(里,ACT_RU_EXECUTION 是运行时执行表。
ACT_RU_EXECUTION` 表的作用
- 一条记录 = 一个执行路径 (Execution)
Execution 是 Flowable 里对“流程运行时的一条执行轨迹”的抽象。- 当流程实例启动时,会创建一个 Root Execution(根执行对象)。
- 每个并行分支、子流程、用户任务、事件等,都会对应自己的 Execution。
换句话说,ACT_RU_EXECUTION 就是存储流程实例的运行时执行树(execution tree)。
表字段含义(核心字段)
| 字段 | 含义 |
|---|---|
| ID_ | 执行对象 ID(主键),唯一标识一个 execution。 |
| PROC_INST_ID_ | 所属流程实例 ID(同一流程实例可能有多个 execution)。 |
| BUSINESS_KEY_ | 流程实例绑定的业务主键(可为空)。 |
| PARENT_ID_ | 父 execution 的 ID(表示层级结构)。 |
| SUPER_EXEC_ | 如果是调用子流程,则记录父流程的 execution ID。 |
| ACT_ID_ | 当前 execution 所在的活动节点 ID(BPMN 节点 ID)。 |
| IS_ACTIVE_ | 是否活跃。1 表示正在执行,0 表示挂起/结束。 |
| IS_CONCURRENT_ | 是否是并行执行。 |
| IS_SCOPE_ | 是否是一个作用域(Scope execution,例如子流程、并行网关等会产生 Scope)。 |
| PROC_DEF_ID_ | 流程定义 ID(指向 ACT_RE_PROCDEF 表)。 |
| REV_ | 乐观锁版本号。 |
举例说明
假设有一个流程定义:
Start → 用户任务A → 并行网关 → 用户任务B、用户任务C → 汇聚网关 → End
执行时,ACT_RU_EXECUTION 可能长这样:
| ID_ | PROC_INST_ID_ | PARENT_ID_ | ACT_ID_ | IS_ACTIVE_ | 描述 |
|---|---|---|---|---|---|
| 1 | 1 | null | startEvent | 0 | 根 execution(流程实例) |
| 2 | 1 | 1 | userTaskA | 1 | 当前正在执行 用户任务A |
| 3 | 1 | 1 | parallelGw | 0 | 并行网关 scope execution |
| 4 | 1 | 3 | userTaskB | 1 | 分支执行 B |
| 5 | 1 | 3 | userTaskC | 1 | 分支执行 C |
可以看到:
PROC_INST_ID_一样,说明都是同一个流程实例- 通过
PARENT_ID_形成一棵 execution tree ACT_ID_表示当前 execution 停留在哪个节点
与其他表的关系
ACT_RU_TASK(任务表)
每个Task都挂在某个 execution 上(EXECUTION_ID_外键)。
任务表里也有PROC_DEF_ID_和PROC_INST_ID_,方便直接查。ACT_RU_VARIABLE(运行时变量表)
流程变量也是挂在 execution 上的。ACT_HI_\*(历史表)
当 execution 结束后,会从 runtime 表删掉,转存到 history 表。
总结一句话:
ACT_RU_EXECUTION 就是 Flowable 引擎里流程实例运行时的“执行树”存储表,所有的任务、变量、事件,都是依附在 Execution 上的。
父 execution
- Execution = 执行对象
每条记录表示“流程运行到某个节点的一个执行路径”。 - Parent Execution = 上一级的执行对象
当流程遇到 分支、子流程、作用域(scope) 时,会产生子 execution。
这个子 execution 的PARENT_ID_指向父 execution。
这样 Execution 之间就形成了一棵 树结构(Execution Tree)。
额外: BPMN XML 的获取逻辑
- 从流程实例查流程定义 ID:
String processDefinitionId = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult()
.getProcessDefinitionId();
- 从流程定义查部署 ID:
ProcessDefinition pd = repositoryService.createProcessDefinitionQuery()
.processDefinitionId(processDefinitionId)
.singleResult();
String deploymentId = pd.getDeploymentId();
- 获取 BPMN XML:
InputStream bpmnStream = repositoryService.getResourceAsStream(
deploymentId,
pd.getResourceName() // 一般是 *.bpmn20.xml
);
String bpmnXml = new BufferedReader(new InputStreamReader(bpmnStream))
.lines().collect(Collectors.joining("\n"));
数据库对应关系
ACT_RE_PROCDEF→deployment_id指向ACT_RE_DEPLOYMENT.idACT_RE_DEPLOYMENT→id对应ACT_GE_BYTEARRAY.deployment_idACT_GE_BYTEARRAY→name对应文件名(比如myProcess.bpmn20.xml)ACT_GE_BYTEARRAY.BYTES_列存放 BPMN XML 的二进制内容
直接查数据库:
SELECT BYTES_
FROM ACT_GE_BYTEARRAY
WHERE DEPLOYMENT_ID_ = '部署ID'
AND NAME_ = '流程文件名.bpmn20.xml';
就能拿到 XML 内容。
- BPMN XML 存在
ACT_GE_BYTEARRAY表里。 - 流程定义信息 存在
ACT_RE_PROCDEF表。 - 部署信息 存在
ACT_RE_DEPLOYMENT表。
更多推荐




所有评论(0)