我們一起玩轉 Flowable 流程例項
今天我們繼續來聊聊流程例項。
部署之後的流程,這個還不能直接執行,例如我們部署了一個請假流程,現在 zhangsan 想要請假,他就需要開啟一個請假流程,lisi 想請假,他也需要開啟一個請假流程,這一個一個開啟的請假流程就是流程例項,而我們一開始部署的請假流程,則類似於一個模版,基於此模版,我們可以開啟很多個具體的流程例項。從這個角度來說,上篇文章我們定義的 ProcessDefinition 就類似於一個 Java 類,今天我們要介紹的 ProcessInstance 則相當於一個 Java 物件。
1. 捋清三個概念
首先我們需要先捋清三個概念:
- 流程定義 ProcessDefinition
- 流程例項 ProcessInstance
- 執行例項 Execution
流程定義
流程定義 ProcessDefinition 這個好說,其實就是我們上篇文章中和大家介紹的內容。將一個流程 XML 檔案部署到 flowable 中,這就是一個定義好的流程了,基於這個定義好的流程,我們可以開啟很多流程例項。
流程例項
流程例項 ProcessInstance 就是通過流程定義啟動的一個流程,他表示一個流程從開始到結束的最大的流程分支,在一個流程中,只存在一個流程例項,流程例項和流程定義的關係就類似於 Java 物件和 Java 類之間的關係。
執行例項
執行例項 Execution 稍微有點難以理解。
首先從類的關係上來看,ProcessInstance 就是 Execution 的子類。
流程例項通常是執行例項的根結點,即在一個流程中,出口和入口可以算是一個流程例項的節點,而中間的過程則是執行例項。
假如流程本身就是一條線,那麼流程例項和執行例項基本上是一樣的,但是如果流程中包含多條線,例如下圖:
這張圖中有並行閘道器,並行任務執行的時候,每一個並行任務就是一個執行例項,這樣大家就好理解了。
結論就是,在一個流程例項中,除了開始和結束之外,其他的都是執行例項。即使流程只有一條線,中間的也都是執行例項,只不過此時的執行例項等於流程例項而已。
好啦,三個基本概念先捋清楚。
2. 五種流程啟動方式
當我們將流程部署好之後,接下來啟動流程,我們有五種不同的方式去啟動一個流程。
- 通過流程定義的 id 去啟動
首先就是通過流程定義的 id 去啟動一個流程,對應的方法名稱就是 RuntimeService#startProcessInstanceById,該方法有好幾個過載的方法,不同的過載方法只是傳遞的引數不同而已,其他基本上都是一樣的。
- 通過流程的 key 去啟動
也可以通過流程定義的 key 去啟動一個流程,根據上篇文章的介紹,大家知道,這個流程定義的 key 其實就是流程 XML 檔案中的 id,這個對應的方法名是 RuntimeService#startProcessInstanceByKey。
- 通過流程的 key+tenantId 去啟動
有這樣一種情況,例如我有兩個子系統 A 和 B,A 和 B 中都有一個請假流程的定義,現在當我想要啟動一個流程的時候,怎麼知道是啟動 A 的請假流程還是啟動 B 的請假流程呢?此時我們可以通過租戶 ID 即 tenantId 去區分,所以,流程啟動就還有一個方法 RuntimeService#startProcessInstanceByKeyAndTenantId。
- 通過流程的 message 去啟動
通過訊息去啟動一個流程,對應的方法是 RuntimeService#startProcessInstanceByMessage。
- 通過流程的 message+tenanId 去啟動
通過訊息+租戶 ID 去啟動一個流程,對應的方法是 RuntimeService#startProcessInstanceByMessageAndTenantId。
3. 簡單實踐
首先我們繪製一個簡單的流程圖,然後按照上篇文章所介紹的方式進行部署,流程圖如下:
流程 XML 檔案如下:
<process id="leave" name="請假流程" isExecutable="true"> <startEvent id="startEvent1" flowable:formFieldValidation="true" flowable:initiator="INITIATOR"></startEvent> <userTask id="sid-EF721F14-B1F1-4B3B-8018-608757EF5391" name="提交請假申請" flowable:assignee="${INITIATOR}" flowable:formFieldValidation="true"> <extensionElements> <modeler:activiti-idm-initiator xmlns:modeler="http://flowable.org/modeler"><![CDATA[true]]></modeler:activiti-idm-initiator> </extensionElements> </userTask> <sequenceFlow id="sid-9C18B4D2-127C-40FD-BC81-1E947628D316" sourceRef="startEvent1" targetRef="sid-EF721F14-B1F1-4B3B-8018-608757EF5391"></sequenceFlow> <userTask id="sid-CC8E2905-C524-4C32-86DE-8C76102EBDF2" name="主管審批" flowable:assignee="zhangsan" flowable:formFieldValidation="true"> <extensionElements> <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete> </extensionElements> </userTask> <sequenceFlow id="sid-62E837FF-DF33-414C-AC21-2DA84E478856" sourceRef="sid-EF721F14-B1F1-4B3B-8018-608757EF5391" targetRef="sid-CC8E2905-C524-4C32-86DE-8C76102EBDF2"></sequenceFlow> <userTask id="sid-D033E54B-4388-46B1-A8F9-472ABD2E1435" name="經理審批" flowable:assignee="lisi" flowable:formFieldValidation="true"> <extensionElements> <modeler:initiator-can-complete xmlns:modeler="http://flowable.org/modeler"><![CDATA[false]]></modeler:initiator-can-complete> </extensionElements> </userTask> <sequenceFlow id="sid-7EDE624F-1D8F-4DF3-BB97-F8D9066A7A75" sourceRef="sid-CC8E2905-C524-4C32-86DE-8C76102EBDF2" targetRef="sid-D033E54B-4388-46B1-A8F9-472ABD2E1435"></sequenceFlow> <endEvent id="sid-FB77ACAC-DB24-4F44-9925-2FE2EAE09EF8"></endEvent> <sequenceFlow id="sid-21345500-1FCF-4356-9FB1-834C09BEA9CB" sourceRef="sid-D033E54B-4388-46B1-A8F9-472ABD2E1435" targetRef="sid-FB77ACAC-DB24-4F44-9925-2FE2EAE09EF8"></sequenceFlow> </process>
這個 XML 檔案我跟大家說一句,在啟動節點上我設定了 flowable:initiator="INITIATOR",相當於定義了流程發起人的變數為 INITIATOR,這個變數名是自定義的,定義好之後,將來我就可以在其他節點中就可以使用這個變量了。
很簡單的流程,其中:
- 提交請假申請是由流程的發起人完成。
- 主管是 zhangsan。
- 經理是 lisi。
好了,先按照上篇文章我們介紹的方式部署流程。
接下來我們要啟動流程,假設我們用流程定義的 key 來啟動一個流程例項:
@SpringBootTest public class RuTest { @Autowired RuntimeService runtimeService; private static final Logger logger = LoggerFactory.getLogger(RuTest.class); @Test void test01() { Authentication.setAuthenticatedUserId("wangwu"); ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave"); logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId()); } }
啟動的程式碼其實很簡單,當流程啟動成功之後,流程中的每一步都會記錄在 ACT_RU_EXECUTION 表中,同時,如果這個節點是一個使用者任務節點(UserTask),那麼同時還會在 ACT_RU_TASK 表中新增一條記錄。
Authentication.setAuthenticatedUserId("wangwu"); 表示設定流程的發起人。
另外一種設定流程發起人的方式如下:
@Autowired IdentityService identityService; @Test void test01() { identityService.setAuthenticatedUserId("wangwu"); ProcessInstance pi = runtimeService.startProcessInstanceByKey("leave"); logger.info("id:{},activityId:{}", pi.getId(), pi.getActivityId()); }
對於我們上面的流程來說,啟動之後,就會進入到提交請假申請這個節點中,所以一共走了兩個節點,那麼 ACT_RU_EXECUTION 表中應該有兩條記錄了,如下圖:
再來看看 ACT_RU_TASK 表中的內容:
可以看到,該表中有一條記錄,這條記錄其實就是提交請假申請這個節點,現在流程就停在這一步了,需要使用者手動操作,才會繼續向下走。
從這兩張表中我們也可以大致上看出來,EXECUTION 和 ProcessInstance 之間的關係,ACT_RU_EXECUTION 表中的每一條記錄就是一個 EXECUTION,多個 EXECUTION 對應同一個 PROC_INST_ID_,而 ACT_RU_TASK 表中的每一條 Task 記錄也都對應了一個 EXECUTION。
現在我們就先去查詢 wangwu 需要完成的 Task(wangwu 是流程的發起人):
@Autowired TaskService taskService; @Test void test02() { List<Task> list = taskService.createTaskQuery().taskAssignee("wangwu").list(); for (Task task : list) { logger.info("id:{};name:{};taskDefinitionKey:{}",task.getId(),task.getName(),task.getTaskDefinitionKey()); } }
根據前面的介紹,我們知道,這個查詢肯定是去 ACT_RU_TASK 表中進行查詢的,我們來看下執行的 SQL:
可以看到,這裡就是根據 ASSIGNEE_ 欄位去查詢任務的。
查詢到任務之後,接下來去完成任務:
@Test void test03() { List<Task> list = taskService.createTaskQuery().taskAssignee("wangwu").list(); for (Task task : list) { taskService.complete(task.getId()); } }
這個表示查詢到 wangwu 的任務然後完成,這個方法執行完成之後,首先會在 ACT_RU_TASK 表中插入一條新的需要 zhangsan 完成的 Task,然後會更新 ACT_RU_EXECUTION 表中對應的執行例項資訊,最後再從 ACT_RU_TASK 表中刪除需要 wangwu 完成的記錄,這些操作是在同一個事務當中完成的。
好了,現在再去執行 test02 的查詢方法,就會發現查不到了,因為沒有 wangwu 需要完成的 task 了,接下來應該去查詢 zhangsan 需要完成的 task。
當一個流程例項完成後,ACT_RU_TASK 和 ACT_RU_EXECUTION 表中的記錄都會被刪除,所以我們可以通過查詢 ACT_RU_EXECUTION 表中是否還有記錄,去判斷一個一個流程目前是處於執行狀態還是完成狀態,程式碼如下:
@Test void test04() { String pId = "9c8557dd-3727-11ed-9404-acde48001122"; ProcessInstance pi = runtimeService.createProcessInstanceQuery().processInstanceId(pId).singleResult(); if (pi == null) { logger.info("{} 流程執行結束", pId); }else{ logger.info("{} 流程正在執行中", pId); } }
最後,如果你想要去 ACT_RU_EXECUTION 表中查詢執行例項也是 OK 的,方式如下:
@Test void test05() { List<Execution> list = runtimeService.createExecutionQuery().processInstanceId("6d0341c7-3729-11ed-8e4e-acde48001122").list(); for (Execution execution : list) { logger.info("id:{};processInstanceId:{};name:{}",execution.getId(),execution.getProcessInstanceId(),execution.getName()); } }
檢視執行的 SQL 如下:
: ==> Preparing: SELECT RES.* , P.KEY_ as ProcessDefinitionKey, P.ID_ as ProcessDefinitionId, P.NAME_ as ProcessDefinitionName, P.VERSION_ as ProcessDefinitionVersion, P.DEPLOYMENT_ID_ as DeploymentId from ACT_RU_EXECUTION RES inner join ACT_RE_PROCDEF P on RES.PROC_DEF_ID_ = P.ID_ WHERE RES.PROC_INST_ID_ = ? order by RES.ID_ asc : ==> Parameters: 6d0341c7-3729-11ed-8e4e-acde48001122(String) : <== Total: 2
可以看到,就是去 ACT_RU_EXECUTION 表中查詢的。
4. 刪除流程例項
如果我們想刪除一個流程例項,操作方式如下:
@Test void test06() { runtimeService.deleteProcessInstance("65ab0b38-38f3-11ed-b103-acde48001122", "javaboy想刪除了"); }
注意這個是刪除正在執行的流程例項資訊,並不會刪除歷史流程資訊。
5. 獲取執行的活動節點
可以根據執行例項的 ID 去查詢活動節點的 ID,方式如下:
@Test void test07() { List<Execution> list = runtimeService.createExecutionQuery().list(); for (Execution execution : list) { List<String> activeActivityIds = runtimeService.getActiveActivityIds(execution.getId()); for (String activeActivityId : activeActivityIds) { System.out.println("activeActivityId = " + activeActivityId); } } }
這裡查詢的其實就是 ACT_RU_EXECUTION 表,查詢到的 activeActivityId 其實就是該表的 ACT_ID 欄位,我們來看下查詢的 SQL:
好啦,流程例項先聊這麼多,下篇文章我們繼續~
- Spring中實現非同步呼叫的方式有哪些?
- 帶引數的全型別 Python 裝飾器
- 整理了幾個Python正則表示式,拿走就能用!
- 設計模式之狀態模式
- 如何實現資料庫讀一致性
- SOLID:開閉原則Go程式碼實戰
- React中如何引入CSS呢
- 慢查詢 MySQL 定位優化技巧,從10s優化到300ms
- 一個新視角:前端框架們都卷錯方向了?
- 編碼中的Adapter,不僅是一種設計模式,更是一種架構理念與解決方案
- 手寫程式語言-遞迴函式是如何實現的?
- 一文搞懂模糊匹配:定義、過程與技術
- 新來個阿里 P7,僅花 2 小時,做出一個多執行緒永動任務,看完直接跪了
- Puzzlescript,一種開發H5益智遊戲的引擎
- @Autowired和@Resource到底什麼區別,你明白了嗎?
- “四招”守護個人資訊保安
- CSS transition 小技巧!如何保留 hover 的狀態?
- React如此受歡迎離不開這4個主要原則
- 我是怎麼入行做風控的
- 重溫三十年前對於 NN 的批判:神經網路無法實現可解釋 AI