news 2026/6/12 18:46:58

Flowable工作流别再直接查act表了!手把手教你设计一张高性能待办已办表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flowable工作流别再直接查act表了!手把手教你设计一张高性能待办已办表

Flowable工作流架构优化:从原生表查询到高性能业务表设计实战

每次打开Flowable的act_ru_task表,看到那些密密麻麻的字段和复杂的关联关系,你是否也感到一阵头疼?特别是在处理多流程、多节点的业务场景时,直接查询原生表不仅SQL复杂难维护,性能更是让人捉襟见肘。本文将带你彻底解决这个痛点,从架构设计到代码实现,手把手教你构建一套高性能的待办已办表系统。

1. 为什么原生表查询会成为性能瓶颈?

Flowable作为一款优秀的工作流引擎,其原生表结构设计得非常通用和灵活。但正是这种通用性,在实际业务场景中往往会带来诸多问题:

  • 字段冗余严重:act_ru_task表包含大量与特定业务无关的字段,如FORM_KEY_、DELEGATION_等
  • 关联查询复杂:获取完整的待办信息通常需要关联act_ru_execution、act_ru_identitylink等多张表
  • 历史数据膨胀:随着流程实例增多,act_hi_taskinst表会快速膨胀,查询效率直线下降
  • 业务适配困难:原生表结构无法直接体现业务属性,如流程类型、审批结果等需要额外处理
-- 典型的原生表复杂查询示例 SELECT t.ID_ AS task_id, t.NAME_ AS task_name, t.CREATE_TIME_ AS create_time, e.BUSINESS_KEY_ AS business_key, u.FIRST_ AS assignee_name FROM act_ru_task t JOIN act_ru_execution e ON t.PROC_INST_ID_ = e.PROC_INST_ID_ LEFT JOIN act_id_user u ON t.ASSIGNEE_ = u.ID_ WHERE t.ASSIGNEE_ = 'user1' AND e.BUSINESS_KEY_ LIKE 'PO%'

2. 高性能业务表设计方案

2.1 核心表结构设计

我们采用业务表与流程表分离的架构,设计了两张核心表:

wf_todo_list(待办主表)

字段名类型描述设计考量
idbigint主键自增主键,避免使用UUID带来的性能问题
system_codevarchar(32)系统标识多系统集成时区分来源
proc_inst_idvarchar(64)流程实例ID与Flowable原生表关联
task_idvarchar(64)任务ID精确关联到具体任务
process_novarchar(64)流程编号业务唯一标识,如"PO202307001"
titlevarchar(255)流程标题展示用,可包含业务关键信息
process_typesmallint流程类型枚举值,如1=采购审批,2=费用报销
node_namevarchar(64)节点名称如"部门经理审批"
assigneevarchar(64)审批人ID关联用户系统
create_timedatetime创建时间精确到秒
approve_timedatetime审批时间精确到秒
approve_resultvarchar(16)审批结果如"同意"、"拒绝"
statustinyint状态0=待办,1=已办,2=撤回
button_typetinyint按钮类型1=办理,2=撤回,3=退回
node_typetinyint节点类型1=普通任务,2=会签
ext_datajson扩展数据存储业务自定义字段
remarkvarchar(255)备注审批意见等

wf_todo_countersign(会签明细表)

字段名类型描述
idbigint主键
todo_idbigint外键关联wf_todo_list
proc_inst_idvarchar(64)流程实例ID
task_idvarchar(64)任务ID
assigneevarchar(64)会签人ID
create_timedatetime创建时间
approve_timedatetime审批时间
approve_resultvarchar(16)审批结果

2.2 索引设计策略

合理的索引设计是保证查询性能的关键:

-- 主表索引 ALTER TABLE wf_todo_list ADD INDEX idx_assignee_status (assignee, status); ALTER TABLE wf_todo_list ADD INDEX idx_proc_inst (proc_inst_id); ALTER TABLE wf_todo_list ADD INDEX idx_process_no (process_no); ALTER TABLE wf_todo_list ADD INDEX idx_create_time (create_time); -- 会签表索引 ALTER TABLE wf_todo_countersign ADD INDEX idx_todo_id (todo_id); ALTER TABLE wf_todo_countersign ADD INDEX idx_assignee (assignee);

3. 数据同步的优雅实现

3.1 全局事件监听器设计

通过实现Flowable的EventListener接口,我们可以捕获流程关键事件并同步数据:

@Component public class TodoEventListener implements EventListener { @Autowired private WfTodoService todoService; @Override public void onEvent(Event event) { if (event instanceof ActivitiEvent) { ActivitiEvent activitiEvent = (ActivitiEvent) event; switch (event.getType()) { case TASK_CREATED: handleTaskCreated((TaskEntity) activitiEvent.getEntity()); break; case TASK_COMPLETED: handleTaskCompleted((TaskEntity) activitiEvent.getEntity()); break; case PROCESS_COMPLETED: handleProcessCompleted(activitiEvent); break; } } } private void handleTaskCreated(TaskEntity task) { // 获取业务变量 Map<String, Object> variables = task.getExecution().getVariables(); WfTodo todo = new WfTodo(); todo.setTaskId(task.getId()); todo.setProcInstId(task.getProcessInstanceId()); todo.setAssignee(task.getAssignee()); todo.setTitle((String) variables.get("title")); todo.setProcessNo((String) variables.get("processNo")); todo.setStatus(0); // 待办 todoService.saveTodo(todo); } // 其他事件处理方法... }

3.2 业务变量传递最佳实践

在启动流程或完成任务时,通过变量传递必要信息:

// 启动流程时设置业务变量 Map<String, Object> variables = new HashMap<>(); variables.put("title", "2023年Q3市场费用报销"); variables.put("processNo", "EXP" + System.currentTimeMillis()); variables.put("applicant", currentUserId); runtimeService.startProcessInstanceByKey("expenseProcess", variables); // 完成任务时更新业务数据 taskService.complete(taskId, Collections.singletonMap("approveResult", "同意"));

4. 查询性能对比与优化

4.1 查询效率实测对比

我们针对三种场景进行了性能测试:

场景原生表查询(ms)业务表查询(ms)提升幅度
单用户待办列表120-15015-207-8倍
流程实例追踪80-10010-156-8倍
会签任务统计200-30025-407-10倍

4.2 高频查询优化示例

场景一:获取用户待办列表

-- 原生表查询 SELECT t.* FROM act_ru_task t WHERE t.ASSIGNEE_ = 'user1' AND t.SUSPENSION_STATE_ = 1 ORDER BY t.CREATE_TIME_ DESC; -- 业务表查询 SELECT * FROM wf_todo_list WHERE assignee = 'user1' AND status = 0 ORDER BY create_time DESC LIMIT 20;

场景二:流程实例任务轨迹

-- 原生表查询(需关联多表) SELECT t.* FROM act_hi_taskinst t JOIN act_hi_procinst p ON t.PROC_INST_ID_ = p.ID_ WHERE p.BUSINESS_KEY_ = 'PO202307001' ORDER BY t.START_TIME_; -- 业务表查询 SELECT * FROM wf_todo_list WHERE process_no = 'PO202307001' ORDER BY create_time;

5. 进阶优化技巧

5.1 历史数据归档策略

随着业务增长,待办表也会积累大量历史数据,建议采用以下策略:

// 定时归档已完成的流程数据 @Scheduled(cron = "0 0 2 * * ?") public void archiveCompletedTodos() { LocalDateTime cutoffDate = LocalDateTime.now().minusMonths(3); // 1. 查询待归档数据 List<WfTodo> completedTodos = todoMapper.selectCompletedBefore(cutoffDate); // 2. 写入归档表 completedTodos.forEach(todo -> { todoArchiveMapper.insert(todo); todoMapper.deleteById(todo.getId()); }); }

5.2 读写分离实现

对于高并发场景,可以采用读写分离架构:

应用服务器 → 业务表主库(写) → 数据同步 → 业务表从库(读)

Spring Boot配置示例:

spring: datasource: write: url: jdbc:mysql://master-db:3306/wf username: user password: pass read: url: jdbc:mysql://slave-db:3306/wf username: user password: pass jpa: properties: hibernate: current_session_context_class: org.springframework.orm.hibernate5.SpringSessionContext

5.3 缓存层优化

对于高频访问的待办数据,引入Redis缓存:

@Service public class TodoCacheService { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String USER_TODO_KEY = "user:todo:%s"; public List<WfTodo> getUserTodos(String userId) { String cacheKey = String.format(USER_TODO_KEY, userId); // 先查缓存 List<WfTodo> cached = (List<WfTodo>) redisTemplate.opsForValue().get(cacheKey); if (cached != null) { return cached; } // 查数据库 List<WfTodo> dbList = todoMapper.findByAssigneeAndStatus(userId, 0); // 写入缓存 redisTemplate.opsForValue().set(cacheKey, dbList, 5, TimeUnit.MINUTES); return dbList; } @EventListener public void handleTodoChange(TodoChangeEvent event) { // 数据变更时清除缓存 String cacheKey = String.format(USER_TODO_KEY, event.getUserId()); redisTemplate.delete(cacheKey); } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 18:42:53

PotPlayer字幕翻译插件:免费实现实时双语字幕的完整方案

PotPlayer字幕翻译插件&#xff1a;免费实现实时双语字幕的完整方案 【免费下载链接】PotPlayer_Subtitle_Translate_Baidu PotPlayer 字幕在线翻译插件 - 百度平台 项目地址: https://gitcode.com/gh_mirrors/po/PotPlayer_Subtitle_Translate_Baidu 还在为外语影视作品…

作者头像 李华
网站建设 2026/6/12 18:35:56

python笔记和练习----少儿编程课程【阶段一(二)】

第13课&#xff1a;字符串是什么&#xff08;认识字符串&#xff09;知识点双引号或者单引号中的数据&#xff0c;就是字符串。用下标方式来确定字符在字符串中的位置。取出某个字符&#xff1a;变量名[下标]。切片是指对操作的对象截取其中一部分的操作。切片的语法&#xff1…

作者头像 李华
网站建设 2026/6/12 18:35:55

HAL层使用sensor2.0,kernel使用sensor AP侧驱动

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、makefile与Kconfig二、AP侧驱动配置1.makefile与Kconfig2.AP侧驱动 注册三、HAL层修改前言 上层使用sensor2.0&#xff0c;其中使用hf manager控制&#x…

作者头像 李华
网站建设 2026/6/12 18:34:52

嵌入式控制平面处理器选型:单线程性能与架构设计的关键考量

1. 项目概述&#xff1a;为什么我们需要关注单线程性能&#xff1f;在嵌入式网络和通信设备的设计中&#xff0c;我们常常陷入一个误区&#xff1a;认为核心越多&#xff0c;性能就必然越强。尤其是在处理数据平面转发这类高度并行化的任务时&#xff0c;多核、众核架构确实大放…

作者头像 李华
网站建设 2026/6/12 18:31:55

Matlab低雷诺数流体仿真工具:2D/3D边界元+2D基本解法一键运行

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;专为低雷诺数粘性流动设计的Matlab仿真工具包&#xff0c;开箱即用&#xff0c;无需编译。内置三种主流数值方法&#xff1a;二维基本解法&#xff08;MFS&#xff09;、二维边界元法&#xff08;BEM&#xff0…

作者头像 李华