资讯动态

Spring Boot 3.x集成Flowable 7.x指南:从依赖到请假审批全流程实践

发布时间:2026/9/8 9:34:21 来源:尧图企业网站定制
Spring Boot 3.x 集成 Flowable 7.x如果在网上搜一圈你会发现资料不少但很多还停留在 Flowable 6.x 搭配 Spring Boot 2.x 的版本照搬到 3.x 上直接报错。这篇我把实际跑通的集成过程完整写出来从依赖引入、自动建表到 BPMN 流程设计、部署、发起实例再到完成一个最简单的请假审批每一步都附上代码和踩坑记录。项目基于 Spring Boot 3.2.x Flowable 7.0.0适合刚接触流程引擎、想快速跑通一个最小闭环的开发者参考。1. 工程初始化与依赖选型思路1.1 为什么选择 Spring Boot 3.x Flowable 7.xSpring Boot 3.x 从 2022 年底发布到现在已经成了 Java 后端新项目的主流选择。它基于 Spring Framework 6整体迁移到了 Jakarta EE 规范javax 包全部变成了 jakarta。这个变化直接导致旧版 Flowable 无法直接运行因为旧版的内部代码大量使用了 javax.servlet、javax.persistence 等 API。Flowable 7.x 是第一个完整适配 Spring Boot 3.x 和 Jakarta 规范的大版本它的模块结构、配置方式跟 6.x 比也有不少调整。如果你现在还在用 6.7.x 配 Spring Boot 2.x那没问题但如果是新项目直接上 7.x 是更省事的选择。选型时还有一个实际问题Flowable 7.x 对 JDK 的要求是 17 起步Spring Boot 3.x 也是一样所以本机 JDK 必须是 17 以上。如果你还在用 JDK 8那思路要调整一下要么升级 JDK要么退回 Spring Boot 2.x Flowable 6.x。1.2 核心依赖坐标与版本对照我这里使用的版本组合比较稳定可以先记一下组件版本JDK17Spring Boot3.2.4Flowable7.0.0MyBatis-Plus可选3.5.5MySQL8.0Flowable 7.x 的 starter 坐标变化不大核心只需要引入一个dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version7.0.0/version /dependency这个 starter 会带入 flowable-engine、flowable-spring、flowable-engine-common 等模块同时也会自动引入 MyBatis 作为 ORM 层。需要特别注意的是Flowable 自带的 MyBatis 版本可能跟你项目里显式声明的 MyBatis 版本冲突。如果你还要集成 MyBatis-Plus建议在引入时排除掉 Flowable 自带的 mybatis避免两个 SqlSessionFactory 打架。如果你不做 MyBatis-Plus 集成只希望流程相关表由 Flowable 自己管理那上面的依赖就够了。但如果业务表也想用 MyBatis-Plus建议这样排掉默认的 mybatisdependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter/artifactId version7.0.0/version exclusions exclusion groupIdorg.mybatis/groupId artifactIdmybatis/artifactId /exclusion exclusion groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId /exclusion /exclusions /dependencyapplication.yml 核心配置spring: datasource: url: jdbc:mysql://localhost:3306/flowable_demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghainullCatalogMeansCurrenttrue username: root password: root1234 driver-class-name: com.mysql.cj.jdbc.Driver flowable: # 关闭自动部署 resources/processes 下的流程文件 auto-deployment-enabled: false # 启动时检查并执行 DDL默认 true database-schema-update: true # 表前缀默认 ACT_ # table-prefix: ACT_ # 历史等级默认 audit history-level: audit # 关闭 Flowable 默认的 IDM 用户体系 idm: enabled: false其中nullCatalogMeansCurrenttrue这个参数一定要加。MySQL 8.0 的驱动在获取元数据时如果不加这个参数Flowable 引擎启动检查表是否存在时会去查一个空 catalog导致每次启动都认为表不存在然后反复执行建表脚本甚至抛异常。这个问题我排查了挺久才定位到先写在这里提醒大家。2. 自动建表机制与 ACT_ 表结构说明2.1 引擎启动时发生了什么Flowable 在 Spring Boot 启动阶段会通过ProcessEngineAutoConfiguration自动创建ProcessEngine。创建过程会经过ProcessEngineConfiguration的初始化其中最关键的一步是检查数据库里是否已经存在 Flowable 所需的表。如果database-schema-update设为true引擎会执行两个动作检查ACT_GE_PROPERTY表是否存在。这张表保存了引擎版本、schema 版本等元数据相当于整个 Flowable 的“户口本”。如果不存在就从 classpath 下找到对应的建表 SQL 脚本然后逐条执行把 60 多张表全部建出来。Flowable 7.x 的建表脚本放在 engine 的 jar 包里的org/flowable/db/create/目录下按数据库类型区分比如mysql、oracle、postgres等。第一次启动时控制台会输出大量 CREATE TABLE 语句同时日志里能看到Flowable database schema update successful这意味着引擎已经完成数据库初始化。2.2 常用表分组与业务对应关系Flowable 的表非常多刚接触会有点懵但实际业务中经常打交道的也就那么几组。我习惯把它们分成五类分组表前缀用途通用数据ACT_GE_通用数据比如属性表、字节数组表流程定义ACT_RE_流程定义、流程模型、流程部署等静态资源运行时ACT_RU_正在执行的流程实例、任务、变量、执行流等历史ACT_HI_已完成的流程实例、任务、活动、附件等身份管理ACT_ID_用户、组、成员关系如果用 Flowable 自带 IDM 才有简单理解ACT_RE_是“设计图纸”保存的是流程怎么定义的ACT_RU_是“施工现状”保存的是现在哪些流程正在进行、谁的任务还没办ACT_HI_是“工程档案”所有办完、走过的记录都在这里。最常用到的几张表ACT_RE_DEPLOYMENT部署记录。每次调用repositoryService.createDeployment().addString(...)部署这里就多一条记录。ACT_RE_PROCDEF流程定义。部署的 BPMN 文件解析后会在这里生成流程定义记录一个流程文件可能包含多个流程通过process标签区分。ACT_RU_EXECUTION执行实例。流程启动后流程实例本身会在这里有一条记录后续分支、子流程的流转都会有对应记录。ACT_RU_TASK待办任务。用户当前需要处理的任务都在这张表相当于你的“待办事项表”。ACT_HI_PROCINST历史流程实例。流程结束、异常终止后流程实例信息归档到这里。ACT_HI_TASKINST历史任务实例。所有任务不管完成还是取消都会留痕。2.3 清库重建的实用技巧开发阶段改模型很频繁经常需要把表全部删掉重新建倒不用手动一张张 DROP。Flowable 提供了一个工具类可以直接清空所有引擎相关数据SpringBootTest class FlowableCleanupTest { Autowired private ProcessEngine processEngine; Test void cleanup() { ProcessEngineConfiguration config processEngine.getProcessEngineConfiguration(); // 删除所有 ACT_ 表 ((ProcessEngineConfigurationImpl) config).getSchemaManager().dropSchema(); } }执行后所有表都会被删除。下一次启动应用时引擎又会基于database-schema-update: true把表重新建出来。不过操作前要确认没有重要数据这个操作不会做任何确认提示执行完就是真的删完了。3. 流程文件设计BPMN 建模与流程定义部署3.1 设计一个最简单的请假审批流程流程引擎的核心资产是 BPMN 文件。我们先用一个非常经典的场景来建流程它包含两条分支路径员工发起请假申请填写请假天数。请假天数小于等于 3 天部门经理审批即可。请假天数大于 3 天需要部门经理审批后再走总经理审批。所有审批通过后流程结束。这个流程看似简单但涵盖了 Flowable 的几类常用元素开始事件startEvent用户任务userTask指派给具体用户或候选组排他网关exclusiveGateway根据条件选一条路走结束事件endEvent3.2 创建 BPMN 文件在src/main/resources/processes/leave.bpmn20.xml下新建文件?xml version1.0 encodingUTF-8? definitions xmlnshttp://www.omg.org/spec/BPMN/20100524/MODEL xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:flowablehttp://flowable.org/bpmn targetNamespacehttp://flowable.org/bpmn process idleaveProcess name请假审批流程 isExecutabletrue startEvent idstartEvent name开始/组件 userTask idapplyTask name提交请假申请 flowable:assignee${applyUser}/ exclusiveGateway idgateway1 name天数判断/ userTask idmanagerTask name部门经理审批 flowable:assignee${managerUser}/ userTask iddirectorTask name总经理审批 flowable:assignee${directorUser}/ endEvent idendEvent name结束/ sequenceFlow idflow1 sourceRefstartEvent targetRefapplyTask/ sequenceFlow idflow2 sourceRefapplyTask targetRefgateway1/ sequenceFlow idflow3 sourceRefgateway1 targetRefmanagerTask conditionExpression xsi:typetFormalExpression ![CDATA[${leaveDays 3}]] /conditionExpression /sequenceFlow sequenceFlow idflow4 sourceRefgateway1 targetRefdirectorTask conditionExpression xsi:typetFormalExpression ![CDATA[${leaveDays 3}]] /conditionExpression /sequenceFlow sequenceFlow idflow5 sourceRefdirectorTask targetRefmanagerTask/ sequenceFlow idflow6 sourceRefmanagerTask targetRefendEvent/ /process /definitions这里的几个细节要特别注意第一flowable:assignee支持${}表达式。这个表达式的取值会在流程启动时从流程变量中解析。上面用了${applyUser}、${managerUser}、${directorUser}意味着启动流程时必须传入这些变量否则任务创建时找不到办理人会直接报错。第二排他网关的条件leaveDays也是流程变量。流程到达网关节点时引擎会取名为leaveDays的变量跟条件表达式里的值比较。所以启动流程时leaveDays也必须存在。第三从总经理审批到部门经理审批的流转线flow5在 BPMN 里就是从directorTask指向managerTask不需要额外网关这就是串行审批的含义先总经理审批再回到部门经理审批。顺序完全由流程线的方向决定。3.3 部署流程文件Flowable 7.x 自动部署默认会扫描classpath:/processes/目录下的.bpmn20.xml和.bpmn文件。我上面配置了auto-deployment-enabled: false主要是不想每次启动都重复部署导致ACT_RE_PROCDEF里堆积多条版本记录。更可控的方式是手动调用 API 部署Service public class ProcessDeployService { Autowired private RepositoryService repositoryService; public Deployment deployProcess(String bpmnResourcePath) { Deployment deployment repositoryService.createDeployment() .addClasspathResource(bpmnResourcePath) .name(请假审批流程) .category(leave) .enableDuplicateFiltering() .deploy(); return deployment; } }enableDuplicateFiltering()是经常被忽略的一个方法。它的作用是如果部署的流程文件内容跟上次部署完全一致就不会重复生成一条 deployment 记录也不会生成新的流程定义版本。这个对开发环境特别友好不会让ACT_RE_PROCDEF表越来越臃肿。调用方式deployService.deployProcess(processes/leave.bpmn20.xml);部署成功后可以查询流程定义Autowired private RepositoryService repositoryService; public ListProcessDefinition listProcessDefinitions() { return repositoryService.createProcessDefinitionQuery() .processDefinitionKey(leaveProcess) .latestVersion() .list(); }processDefinitionKey就是我们 BPMN 里process idleaveProcess这个 id。上线的流程经常有多次修改所以一个 key 可能对应多个版本通过latestVersion()能拿到最新版。3.4 流程定义版本机制背后的逻辑为什么要维护多个版本因为流程定义在运行过程中是“不可变”的。引擎启动某个流程实例后这个实例会绑定当时那个版本的流程定义哪怕后来流程文件被重新部署了正在运行的旧实例仍然按照旧版本执行直到它走完或终止。这也是 Flowable 遵循 BPMN 规范的典型设计。所以线上改流程时正常流程是编辑 BPMN 文件调整节点或条件。重新部署得到一个新版本。未来的新流程实例自动用新版本旧实例继续跑旧版本。如果你想“一刀切”全部用新版本就需要在流程实例启动后手动迁移或者通过定义挂起/激活的方式控制这块是更高级的内容后面有机会单独写。4. 流程发起、任务查询与完成4.1 发起流程实例部署完成后下一步是启动流程实例。启动时先说说流程变量的设计思路。Flowable 的流程变量本质上是 KV 数据存储在ACT_RU_VARIABLE表里随流程实例绑定。这里有三个变量是流程定义中引用的applyUser谁发起请假会被flowable:assignee${applyUser}解析。managerUser部门经理通常是组织架构里查出来的上级。directorUser总经理大于 3 天时才需要。leaveDays请假天数用于网关条件判断。启动代码Service public class LeaveProcessService { Autowired private RuntimeService runtimeService; public ProcessInstance startLeaveProcess(String applyUser, String managerUser, String directorUser, int leaveDays, String reason) { MapString, Object variables new HashMap(); variables.put(applyUser, applyUser); variables.put(managerUser, managerUser); variables.put(directorUser, directorUser); variables.put(leaveDays, leaveDays); variables.put(reason, reason); ProcessInstance processInstance runtimeService .createProcessInstanceBuilder() .processDefinitionKey(leaveProcess) .name(小李的请假申请- System.currentTimeMillis()) .variables(variables) .start(); return processInstance; } }createProcessInstanceBuilder()是 Flowable 推荐的启动方式它比老的startProcessInstanceByKey更灵活后续可以设置业务 key、租户 ID 等。processDefinitionKey是流程定义 key写死leaveProcess时默认启动最新版本。4.2 待办任务查询流程启动后引擎会按照 BPMN 的连线自动走到第一个用户任务applyTask也就是“提交请假申请”这个节点。即使它指派人就是发起人自己任务也已经是待办状态了。查询某人待办的逻辑Service public class TaskQueryService { Autowired private TaskService taskService; public ListTask queryTodoTasks(String assignee) { return taskService.createTaskQuery() .taskAssignee(assignee) .active() .orderByTaskCreateTime() .desc() .list(); } }taskAssignee精确匹配ACT_RU_TASK.ASSIGNEE_字段。如果业务流程用的是候选组candidateGroups那就改成taskCandidateGroup(managerGroup)或者taskCandidateUser(zhangsan)查询。任务对象里有几个关键字段实际开发中会用得多字段含义task.getId()任务 ID完成任务时的入参task.getProcessInstanceId()所属流程实例 IDtask.getTaskDefinitionKey()节点 ID对应 BPMN 里 userTask 的 idtask.getAssignee()当前办理人task.getCreateTime()任务创建时间4.3 完成任务我们要完成的是“提交请假申请”这个任务然后流程会自动流向网关判断天数。完成任务时可以通过TaskService设置本地变量和流程变量Service public class LeaveCompleteService { Autowired private TaskService taskService; public void completeApplyTask(String taskId, String comment) { MapString, Object variables new HashMap(); variables.put(applyComment, comment); // 添加审批意见通过 flowable 注入的 comment taskService.addComment(taskId, null, comment); // 完成任务并更新流程变量 taskService.complete(taskId, variables); } }addComment会把审批意见写入ACT_HI_COMMENT表这是流程审批里非常常见的操作。complete时的variables会在完成任务后合并到流程变量中。注意一点variables里的 key 如果跟已有流程变量重复会覆盖已有值。所以在审批节点要小心不要用跟启动变量相同的 key 塞进去一个不同类型的值否则后续条件判断可能出错。4.4 条件网关如何自动流转完成applyTask后引擎把执行流推到gateway1排他网关。排他网关会按连线顺序逐条检查出口条件先看leaveDays 3如果表达式结果为true就走 branch1 到部门经理审批如果false再看下一条leaveDays 3走总经理审批。如果所有条件都不满足引擎会在运行时报错FlowableException: No outgoing sequence flow of the exclusive gateway could be selected for continuing the process排查办法很直接打开ACT_RU_VARIABLE表看leaveDays的类型和取值再对照 xml 里的条件表达式。类型不匹配是常见坑比如用字符串3存进去表达式写成${leaveDays 3}判断时就是字符串比较结果可能完全不对。5. 完整跑通一个请假实例5.1 全部操作的调用顺序把前面的服务串起来写一个测试用例来完整验证流程SpringBootTest class LeaveProcessIntegrationTest { Autowired private ProcessDeployService deployService; Autowired private LeaveProcessService processService; Autowired private TaskQueryService taskQueryService; Autowired private LeaveCompleteService completeService; Autowired private HistoryService historyService; Test void testLeaveProcessFullFlow() { // 1. 部署流程 deployService.deployProcess(processes/leave.bpmn20.xml); // 2. 发起 5 天请假走总经理审批 ProcessInstance instance processService.startLeaveProcess( zhangsan, lisi, wangwu, 5, 年假); // 3. 查询发起人的待办 ListTask applyTasks taskQueryService.queryTodoTasks(zhangsan); Task applyTask applyTasks.stream() .filter(t - t.getProcessInstanceId().equals(instance.getId())) .findFirst() .orElseThrow(() - new RuntimeException(未找到申请任务)); // 4. 完成申请任务 completeService.completeApplyTask(applyTask.getId(), 提交请假申请); // 5. 此时待办应该在总经理 wangwu 那里 ListTask directorTasks taskQueryService.queryTodoTasks(wangwu); Assertions.assertFalse(directorTasks.isEmpty()); Task directorTask directorTasks.stream() .filter(t - t.getProcessInstanceId().equals(instance.getId())) .findFirst() .orElseThrow(() - new RuntimeException(未找到总经理审批任务)); // 6. 总经理通过 completeService.completeApplyTask(directorTask.getId(), 同意请部门经理确认); // 7. 流转到部门经理 lisi ListTask managerTasks taskQueryService.queryTodoTasks(lisi); Task managerTask managerTasks.stream() .filter(t - t.getProcessInstanceId().equals(instance.getId())) .findFirst() .orElseThrow(() - new RuntimeException(未找到部门经理任务)); completeService.completeApplyTask(managerTask.getId(), 同意); // 8. 流程应该结束了 HistoryProcessInstance history historyService.createHistoryProcessInstanceQuery() .processInstanceId(instance.getId()) .singleResult(); Assertions.assertNotNull(history.getEndTime()); } }注意这里流程顺序跟直觉有点不一样大于 3 天时总经理先审批然后再到部门经理。原因是 BPMN 连线里directorTask的下一条线直接连到了managerTask。你可以按实际业务调整线的方向BPMN 的灵活性就在这里不用改代码改文件重新部署就行。5.2 业务流程与数据库表变化的对应执行完这个测试数据库里会留下这些变化表记录内容ACT_RE_DEPLOYMENT一条部署记录ACT_RE_PROCDEF一条流程定义ACT_RU_EXECUTION流程实例执行记录流程结束后这里会清掉ACT_RU_TASK当前待办任务流程结束时清空ACT_RU_VARIABLE流程变量applyUser、leaveDays 等ACT_HI_PROCINST历史流程实例包含开始时间和结束时间ACT_HI_TASKINST历史任务实例每一步任务的执行记录ACT_HI_ACTINST历史活动实例每个节点的进入、离开时间ACT_HI_COMMENT审批批注addComment 写入的内容流程结束后ACT_RU_开头的运行态数据基本清空ACT_HI_开头的历史数据保留。这个设计很符合实际运行时表追求轻量只放当前必须的数据历史表做归档方便后续审计和统计。5.3 流程实例的业务 Key 用法业务系统对接流程引擎时最怕一件事流程实例跟业务单据对不上。比如一条请假单对应一个流程实例怎么通过请假单 ID 反查流程实例怎么从流程实例反查请假单Flowable 为此提供了businessKey机制。启动时设置ProcessInstance instance runtimeService .createProcessInstanceBuilder() .processDefinitionKey(leaveProcess) .businessKey(LEAVE-20240507-001) .variables(variables) .start();之后就可以通过业务单据号查询流程实例ProcessInstance instance runtimeService.createProcessInstanceQuery() .processInstanceBusinessKey(LEAVE-20240507-001) .singleResult();这是流程引擎和业务系统解耦的关键比用processInstanceId关联业务表更自然。6. 常见问题与排查技巧实录6.1 启动失败Flowable tables not found如果启动时日志报Could not find Flowable tables in database先确认database-schema-update配置是否正确是否显式设为false。如果确认是true检查连接串里有没有nullCatalogMeansCurrenttrue。MySQL 8.0 的驱动默认nullCatalog行为会让 Flowable 误判“表不存在”进而反复建表或抛异常。还有一个可能数据库账号权限不足。建表需要CREATE、ALTER、DROP、INDEX权限如果账号只有 DML 权限Schema 初始化那步会直接权限报错。6.2 任务查询查不到数据这种情况九成是查询条件写错了。taskAssignee(zhangsan)匹配的是ACT_RU_TASK.ASSIGNEE_而 assignee 的赋值来自 BPMN 里的${applyUser}。如果你流程定义写的是flowable:candidateUserszhangsan,lisi那是候选用户语义不是办理人用taskAssignee查永远查不到。此时用taskCandidateUser(zhangsan)或者taskCandidateOrAssigned(zhangsan)查。6.3 排他网关没有符合条件的出口上面提过所有出口条件不匹配就会抛No outgoing sequence flow could be selected。排查三步走查ACT_RU_VARIABLE确认条件变量是否存在、类型正确。检查表达式语法Flowable 用表达式引擎执行${...}不是简单的字符串匹配。检查网关出口连线顺序是否合理条件是否覆盖了所有可能取值。比如请假天数可能出现负数那你leaveDays 3和leaveDays 3都覆盖到了但如果是枚举状态漏了某个取值就会出问题。建议最后加一条兜底连线默认走某个节点避免运行时异常。6.4 事务什么时候回滚Flowable 的 Service 方法默认参与 Spring 事务。比如complete方法内部会更新任务状态、推进执行流、写历史数据这些操作都在同一个事务里。如果其中任何一步报错全部回滚任务还是待办状态不会出现“任务没了但流程卡住”的中间态。但要注意addComment和complete如果分开调用它们各自是独立事务。如果addComment成功了complete失败就会出现“批注写了但任务没完成”的问题。所以在同一次事务里完成Transactional(rollbackFor Exception.class) public void completeTaskWithComment(String taskId, String comment) { taskService.addComment(taskId, null, comment); taskService.complete(taskId); }6.5 部署时流程定义重复丢失如果每次启动应用都重新部署会生成很多版本记录。建议保持enableDuplicateFiltering()的配置或者干脆把自动部署关了通过手动接口部署。还有一个细节ACT_RE_DEPLOYMENT存储了部署的字节数组资源默认存在ACT_GE_BYTEARRAY表是 BLOB 类型。如果 BPMN 文件比较大或者部署次数很多那张表会比较大。定期清理旧版本部署运营上会比较省心repositoryService.deleteDeployment(deploymentId, true);第二个参数true表示级联删除会把该 deployment 关联的流程定义、运行时数据、历史数据一并删掉。操作前建议做好备份这个动作没有撤销按钮。6.6 REST API 与工作流引擎分离的考虑Flowable 自带一套 REST API只需要引入flowable-spring-boot-starter-rest就能通过 HTTP 接口操作流程。很多团队图省事直接对外开放结果安全性和可维护性都成问题Flowable REST 接口的权限模型比较弱默认不暴露给公网最多在内网或网关后面用。我更推荐的做法是不引入 REST starter自己封装业务接口内部调用RuntimeService、TaskService、HistoryService对外暴露符合业务语义的 API比如/api/leave/start、/api/leave/todo、/api/leave/complete。这样流程引擎内部细节不会泄漏到前端也能在接口层做权限校验、参数校验、业务状态管理。个人在实际项目里还有一个体会流程引擎表最好独立 schema 或独立数据库不要跟业务表混在一起。不是技术上不能混而是备份、恢复、迁移时业务数据和流程数据混在一起会很痛苦。独立部署后流程引擎升级、换存储、导数据都清爽很多。最后再分享一个小技巧由于 Flowable 的流程定义在运行时是不变的开发阶段改 BPMN 特别频繁一定要在本地开发环境配置auto-deployment-enabled: false然后通过一个管理接口手动部署避免每改一次 BPMN 就重启一次应用。这个习惯能省下大量无效等待而且让部署行为变得可控、可追溯。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价