1. Flowable历史数据基础概念解析
第一次接触Flowable的历史数据模块时,我完全被那些以ACT_HI_开头的数据库表搞晕了。直到在某个深夜加班调试流程时,突然意识到这些历史数据就像是流程世界的"黑匣子",完整记录了每个流程实例从生到死的全部轨迹。
Flowable的历史数据模块主要包含六大核心实体,它们各自承担着不同的记录职责:
HistoricProcessInstance:流程实例的"人生档案",记录流程从启动到结束的关键时间节点。我常把它比作快递物流信息,能看到流程什么时候发货(startTime)、什么时候签收(endTime)
HistoricActivityInstance:每个流程节点的"监控录像",精确到秒级记录节点执行情况。最近排查一个审批卡顿问题时,就是通过它发现某个审批节点平均耗时高达47秒
HistoricVariableInstance:流程变量的"时光胶囊",保存着流程运行过程中所有变量的最终状态。上周财务部门需要三年前的某个报销流程的审批金额,就是从这里挖出来的
HistoricTaskInstance:人工任务的"工作日志",包含任务分配人、处理时间等关键信息。人力资源部做绩效考核时,这些数据就是重要依据
HistoricDetail:流程细节的"显微镜",可以追踪变量变更记录。曾用它找出某个订单价格被多次修改的完整记录
HistoricIdentityLink:参与者的"通讯录",记录所有参与过流程的人员信息
与运行时数据相比,历史数据有三大特点:
- 持久性:流程结束后依然存在,不像运行时数据会被清理
- 只读性:只能查询不能修改,确保数据真实性
- 分析友好:包含完整的生命周期信息,适合做统计分析
// 典型的历史数据表结构示例 ACT_HI_PROCINST (流程实例表) |-- ID_ // 主键 |-- PROC_DEF_ID_ // 流程定义ID |-- START_TIME_ // 开始时间 |-- END_TIME_ // 结束时间 |-- DURATION_ // 总耗时(毫秒) |-- START_USER_ID_// 发起人2. 历史流程实例查询实战
去年我们系统出现一个诡异现象:某些流程实例会无故消失。最后就是通过历史流程查询锁定了问题——某个定时任务误删了运行中的数据,但历史表里还完整保留着这些流程的"遗照"。
2.1 基础查询方法
最常用的查询场景莫过于查找特定状态的流程实例。比如要查询所有已完成的采购审批流程:
List<HistoricProcessInstance> processes = historyService .createHistoricProcessInstanceQuery() .processDefinitionKey("purchaseApproval") .finished() // 只查已结束的 .list();几个实用的查询技巧:
.unfinished()查找进行中的流程.processDefinitionKey()按流程类型过滤.startedBy("user1")按发起人过滤
2.2 高级查询技巧
当需要做流程效能分析时,这些查询特别有用:
// 查询耗时最长的10个流程 List<HistoricProcessInstance> slowProcesses = historyService .createHistoricProcessInstanceQuery() .orderByProcessInstanceDuration().desc() .listPage(0, 10); // 查询某时间段内发起的流程 Date startDate = Date.from(LocalDate.now().minusDays(7).atStartOfDay(ZoneId.systemDefault()).toInstant()); Date endDate = new Date(); List<HistoricProcessInstance> recentProcesses = historyService .createHistoricProcessInstanceQuery() .startedAfter(startDate) .startedBefore(endDate) .list();2.3 元数据查询
有时我们需要获取流程的关联信息:
HistoricProcessInstance instance = historyService .createHistoricProcessInstanceQuery() .processInstanceId("123") .includeProcessVariables() // 包含变量 .singleResult(); Map<String, Object> variables = instance.getProcessVariables(); String businessKey = instance.getBusinessKey();提示:当查询大量历史数据时,建议使用
.listPage(start, size)分页查询,避免内存溢出
3. 历史变量查询的妙用
曾经处理过一个棘手的需求:需要找出所有修改过合同金额的审批流程。历史变量查询就像时光机,带我找出了所有动过金额字段的流程记录。
3.1 基础变量查询
// 查询某个流程的所有历史变量 List<HistoricVariableInstance> variables = historyService .createHistoricVariableInstanceQuery() .processInstanceId("123") .list(); // 查询特定变量的变更记录 HistoricVariableInstance amountVar = historyService .createHistoricVariableInstanceQuery() .processInstanceId("123") .variableName("contractAmount") .singleResult();3.2 变量类型处理
Flowable支持多种变量类型,查询时需要特别注意:
// 处理不同类型变量 variables.forEach(var -> { if(var.getVariableTypeName().equals("string")) { String value = (String)var.getValue(); // 处理字符串值 } else if(var.getVariableTypeName().equals("long")) { Long value = (Long)var.getValue(); // 处理数值 } });3.3 变量变更追踪
要查看变量的完整修改记录,需要使用HistoricDetail:
List<HistoricDetail> changes = historyService .createHistoricDetailQuery() .variableUpdates() .processInstanceId("123") .variableName("contractAmount") .orderByTime().asc() .list(); changes.forEach(change -> { HistoricVariableUpdate update = (HistoricVariableUpdate)change; System.out.println(update.getTime() + ": " + update.getValue()); });4. 历史活动与任务查询实战
上个月做流程优化时,我发现审批流程的平均耗时比预期长很多。通过历史活动查询,最终定位到问题出在"财务复核"环节,该环节平均耗时高达2.3天。
4.1 活动实例查询
// 查询某个流程的所有活动节点 List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery() .processInstanceId("123") .orderByHistoricActivityInstanceStartTime().asc() .list(); // 查询特定类型的活动 List<HistoricActivityInstance> userTasks = historyService .createHistoricActivityInstanceQuery() .activityType("userTask") .processInstanceId("123") .list();4.2 任务实例查询
// 查询某用户处理过的所有任务 List<HistoricTaskInstance> tasks = historyService .createHistoricTaskInstanceQuery() .taskAssignee("zhangsan") .finished() .orderByHistoricTaskInstanceEndTime().desc() .list(); // 查询超时任务 long threshold = 1000 * 60 * 60 * 24; // 24小时 List<HistoricTaskInstance> overdueTasks = historyService .createHistoricTaskInstanceQuery() .finished() .taskDurationGreaterThan(threshold) .list();4.3 活动耗时分析
这个代码帮我找出了流程中的性能瓶颈:
List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery() .processDefinitionId("leaveApproval:1:123") .finished() .list(); Map<String, List<Long>> durationByActivity = activities.stream() .collect(Collectors.groupingBy( HistoricActivityInstance::getActivityId, Collectors.mapping( ai -> ai.getDurationInMillis(), Collectors.toList() ) )); durationByActivity.forEach((activityId, durations) -> { double avg = durations.stream().mapToLong(l -> l).average().orElse(0); System.out.println(activityId + "平均耗时:" + (avg / 1000 / 60) + "分钟"); });5. 高级历史数据应用场景
5.1 流程版本对比
当流程定义升级后,我们常需要对比不同版本的表现:
// 查询v1.0版本的流程实例 List<HistoricProcessInstance> v1Instances = historyService .createHistoricProcessInstanceQuery() .processDefinitionKey("loanApproval") .processDefinitionVersion(1) .finished() .list(); // 查询v2.0版本的流程实例 List<HistoricProcessInstance> v2Instances = historyService .createHistoricProcessInstanceQuery() .processDefinitionKey("loanApproval") .processDefinitionVersion(2) .finished() .list(); // 比较平均处理时间 double v1Avg = calculateAverageDuration(v1Instances); double v2Avg = calculateAverageDuration(v2Instances);5.2 流程轨迹重现
通过组合多种历史数据,可以完整重现流程执行过程:
public void replayProcess(String processInstanceId) { // 获取流程实例 HistoricProcessInstance instance = historyService .createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); System.out.println("流程[" + instance.getId() + "]轨迹:"); System.out.println("发起时间:" + instance.getStartTime()); // 获取所有活动节点 List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime().asc() .list(); // 打印节点轨迹 activities.forEach(act -> { System.out.printf("%s - %s (%s~%s)\n", act.getActivityName(), act.getActivityType(), act.getStartTime(), act.getEndTime()); }); System.out.println("结束时间:" + instance.getEndTime()); }5.3 历史数据清理
随着系统运行,历史数据会不断膨胀,需要定期清理:
// 删除3个月前的完成的历史流程 Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.MONTH, -3); Date beforeDate = calendar.getTime(); historyService.createHistoricProcessInstanceDeleteBuilder() .finishedBefore(beforeDate) .deleteWithRelatedData(); // 同时删除关联数据注意:删除操作不可逆,建议先做备份。我们曾经误删过一次数据,现在都会先执行
.count()确认影响范围