在之前的文章中松哥和小伙伴們聊過,正在執(zhí)行的流程信息是保存在以 ACT_RU_ 為前綴的表中,執(zhí)行完畢的流程信息則保存在以 ACT_HI_ 為前綴的表中,也就是流程歷史信息表,當然這個歷史信息表繼續(xù)細分的話,還有好多種,今天我們就來聊一聊這個話題。
假設(shè)我有如下一個流程:

當這個流程執(zhí)行完畢后,以 ACT_RU_ 為前綴的表中的數(shù)據(jù)均已清空,現(xiàn)在如果想查看剛剛執(zhí)行過的流程信息,我們就得去以 ACT_HI_ 為前綴的表中。
1. 歷史流程信息
歷史流程信息查看,方式如下:
@Test
void test05(){
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().finished().list();
for (HistoricProcessInstance hpi : list) {
logger.info("name:{},startTime:{},endTime:{}",hpi.getName(),hpi.getStartTime(),hpi.getEndTime());
}
}
調(diào)用的時候執(zhí)行的 finished() 方法表示查詢已經(jīng)執(zhí)行完畢的流程信息(從這里也可以看出,對于未執(zhí)行完畢的流程信息也會保存在歷史表中)。
我們來看下這個查詢對應的 SQL,如下:
SELECT RES.* , DEF.KEY_ as PROC_DEF_KEY_, DEF.NAME_ as PROC_DEF_NAME_, DEF.VERSION_ as PROC_DEF_VERSION_, DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_ from ACT_HI_PROCINST RES left outer join ACT_RE_PROCDEF DEF on RES.PROC_DEF_ID_ = DEF.ID_ WHERE RES.END_TIME_ is not NULL order by RES.ID_ asc
從這個 SQL 中可以看到,這個查詢本質(zhì)上就是查詢的 ACT_HI_PROCINST 表。如下圖:

如果我們在查詢的時候不限制流程是否執(zhí)行完畢,那么我們的查詢方法如下:
@Test
void test05(){
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery().list();
for (HistoricProcessInstance hpi : list) {
logger.info("name:{},startTime:{},endTime:{}",hpi.getName(),hpi.getStartTime(),hpi.getEndTime());
}
}
對應的查詢 SQL 如下:
SELECT RES.* , DEF.KEY_ as PROC_DEF_KEY_, DEF.NAME_ as PROC_DEF_NAME_, DEF.VERSION_ as PROC_DEF_VERSION_, DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_ from ACT_HI_PROCINST RES left outer join ACT_RE_PROCDEF DEF on RES.PROC_DEF_ID_ = DEF.ID_ order by RES.ID_ asc
和前面的 SQL 相比,后面的 SQL 少了 WHERE RES.END_TIME_ is not NULL 條件,也就是說,判斷一個流程是否執(zhí)行完畢,就看它的 END_TIME_ 是否為空,不為空就表示流程已經(jīng)執(zhí)行結(jié)束了,為空就表示流程尚在執(zhí)行中。
2. 歷史任務查詢
剛剛我們查詢的是歷史流程,接下來我們來看下歷史任務,也就是查詢一個流程中執(zhí)行過的 Task 信息,如下表示查詢所有的歷史流程任務:
@Test
void test06(){
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().list();
for (HistoricTaskInstance hti : list) {
logger.info("name:{},assignee:{},createTime:{},endTime:{}",hti.getName(),hti.getAssignee(),hti.getCreateTime(),hti.getEndTime());
}
}
這個查詢對應的 SQL 如下:
SELECT RES.* from ACT_HI_TASKINST RES order by RES.ID_ asc
可以看到,歷史任務表就是 ACT_HI_TASKINST,如下圖:

當然,這里還有很多其他的玩法,例如查詢某一個流程已經(jīng)執(zhí)行完畢的歷史任務,如下:
@Test
void test07(){
List<HistoricProcessInstance> instanceList = historyService.createHistoricProcessInstanceQuery().list();
for (HistoricProcessInstance hpi : instanceList) {
List<HistoricTaskInstance> list = historyService.createHistoricTaskInstanceQuery().processInstanceId(hpi.getId()).finished().list();
for (HistoricTaskInstance hti : list) {
logger.info("name:{},assignee:{},createTime:{},endTime:{}", hti.getName(), hti.getAssignee(), hti.getCreateTime(), hti.getEndTime());
}
}
}
這個里邊的查詢歷史任務的 SQL 如下:
SELECT RES.* from ACT_HI_TASKINST RES WHERE RES.PROC_INST_ID_ = ? and RES.END_TIME_ is not null order by RES.ID_ asc
可以看到,跟前面相比,多了兩個條件:
- 流程實例 ID
- 流程結(jié)束時間不為 null
從這里也可以看出來,這個 finish 方法的執(zhí)行邏輯跟我們前面講的是一樣的。
3. 歷史活動查詢
歷史任務就是各種 Task,歷史活動則包括跟多內(nèi)容,像開始/結(jié)束節(jié)點,連線等等這些信息都算是活動,這個在之前的文章中松哥已經(jīng)和大家介紹過了。
查詢代碼如下:
@Test
void test08(){
List<HistoricActivityInstance> list = historyService.createHistoricActivityInstanceQuery().list();
for (HistoricActivityInstance hai : list) {
logger.info("name:{},startTime:{},assignee:{},type:{}",hai.getActivityName(),hai.getStartTime(),hai.getAssignee(),hai.getActivityType());
}
}
這個查詢對應的 SQL 如下:
SELECT RES.* from ACT_HI_ACTINST RES order by RES.ID_ asc
可以看到,ACT_HI_ACTINST 表中保存了歷史活動信息。
4. 歷史變量查詢
查詢流程執(zhí)行的歷史變量,方式如下:
@Test
void test09(){
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().singleResult();
List<HistoricVariableInstance> list = historyService.createHistoricVariableInstanceQuery().processInstanceId(pi.getId()).list();
for (HistoricVariableInstance hvi : list) {
logger.info("name:{},type:{},value:{}", hvi.getVariableName(), hvi.getVariableTypeName(), hvi.getValue());
}
}
這個查詢對應的 SQL 如下:
SELECT RES.* from ACT_HI_VARINST RES WHERE RES.PROC_INST_ID_ = ? order by RES.ID_ asc
可以看到流程的歷史變量信息保存在 ACT_HI_VARINST 表中。
5. 歷史日志查詢
有的小伙伴看到日志這兩個字可能會覺得奇怪,咦?流程執(zhí)行還有日志嗎?沒聽說過呀!
其實歷史日志查詢就是前面那幾種的一個集大成者,用法如下:
@Test
void test10(){
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().singleResult();
ProcessInstanceHistoryLog historyLog = historyService.createProcessInstanceHistoryLogQuery(pi.getId())
//包括歷史活動
.includeActivities()
//包括歷史任務
.includeTasks()
//包括歷史變量
.includeVariables()
.singleResult();
logger.info("id:{},startTime:{},endTime:{}", historyLog.getId(), historyLog.getStartTime(), historyLog.getEndTime());
List<HistoricData> historicData = historyLog.getHistoricData();
for (HistoricData data : historicData) {
if (data instanceof HistoricActivityInstance) {
HistoricActivityInstance hai = (HistoricActivityInstance) data;
logger.info("name:{},type:{}", hai.getActivityName(), hai.getActivityType());
}
if (data instanceof HistoricTaskInstance) {
HistoricTaskInstance hti = (HistoricTaskInstance) data;
logger.info("name:{},assignee:{}", hti.getName(), hti.getAssignee());
}
if (data instanceof HistoricVariableInstance) {
HistoricVariableInstance hvi = (HistoricVariableInstance) data;
logger.info("name:{},type:{},value:{}", hvi.getVariableName(), hvi.getVariableTypeName(), hvi.getValue());
}
}
}
這個里邊,首先是查詢基本的流程日志信息,這個本質(zhì)上就是查詢歷史流程實例信息,對應的 SQL 如下:
select RES.*, DEF.KEY_ as PROC_DEF_KEY_, DEF.NAME_ as PROC_DEF_NAME_, DEF.VERSION_ as PROC_DEF_VERSION_, DEF.DEPLOYMENT_ID_ as DEPLOYMENT_ID_ from ACT_HI_PROCINST RES left outer join ACT_RE_PROCDEF DEF on RES.PROC_DEF_ID_ = DEF.ID_ where PROC_INST_ID_ = ?
接下來我寫了三個 include,每一個 include 都對應一句 SQL:
includeActivities 對應的 SQL 如下:
SELECT RES.* from ACT_HI_ACTINST RES WHERE RES.PROC_INST_ID_ = ? order by RES.ID_ asc
includeTasks 對應的 SQL 如下:
SELECT RES.* from ACT_HI_TASKINST RES WHERE RES.PROC_INST_ID_ = ? order by RES.ID_ asc
includeVariables 對應的 SQL 如下:
SELECT RES.* from ACT_HI_VARINST RES WHERE RES.PROC_INST_ID_ = ? order by RES.ID_ asc
最終查詢完成后,調(diào)用 getHistoricData 方法可以查看這些額外的數(shù)據(jù),List 集合中存放的 HistoricData 也分為不同的類型:
- includeActivities 方法對應最終查詢出來的類型是 HistoricActivityInstance。
- includeTasks 方法對應最終查詢出來的類型是 HistoricTaskInstance。
- includeVariables 方法對應最終查詢出來的類型是 HistoricVariableInstance。
在遍歷的時候通過類型判斷去查看具體是哪一種變量類型。
綜上,這個歷史日志查詢其實就是一個集大成者。
6. 歷史權(quán)限查詢
這個是用來查詢流程或者任務的處理人,例如查詢流程的處理人,方式如下:
@Test
void test11(){
HistoricProcessInstance pi = historyService.createHistoricProcessInstanceQuery().singleResult();
List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForProcessInstance(pi.getId());
for (HistoricIdentityLink link : links) {
logger.info("userId:{}",link.getUserId());
}
}
這個是查詢流程對應的處理人,對應的 SQL 如下:
select * from ACT_HI_IDENTITYLINK where PROC_INST_ID_ = ?
如果想查詢?nèi)蝿盏奶幚砣?,對應的方式如下?/p>
@Test
void test12(){
String taskName = "提交請假申請";
HistoricTaskInstance hti = historyService.createHistoricTaskInstanceQuery().taskName(taskName).singleResult();
List<HistoricIdentityLink> links = historyService.getHistoricIdentityLinksForTask(hti.getId());
for (HistoricIdentityLink link : links) {
logger.info("{} 任務的處理人是 {}",taskName,link.getUserId());
}
}
這個查詢對應的 SQL 如下:
select * from ACT_HI_IDENTITYLINK where TASK_ID_ = ?
和前面的相比,其實就多了一個查詢條件 TASK_ID_。
7. 自定義查詢 SQL
和前面講的很多查詢類似,當我們弄懂了每一個歷史查詢的 API 操作的是哪一個數(shù)據(jù)表,就會發(fā)現(xiàn),歷史數(shù)據(jù)的查詢,也可以自定義 SQL。
舉個例子和小伙伴們看下,例如查詢某一個流程已經(jīng)執(zhí)行完畢的歷史任務:
@Test
void test13(){
List<HistoricProcessInstance> instanceList = historyService.createHistoricProcessInstanceQuery().list();
for (HistoricProcessInstance hpi : instanceList) {
List<HistoricTaskInstance> list = historyService.createNativeHistoricTaskInstanceQuery()
.sql("SELECT RES.* from ACT_HI_TASKINST RES WHERE RES.PROC_INST_ID_ = #{pid} and RES.END_TIME_ is not null order by RES.ID_ asc")
.parameter("pid",hpi.getId()).list();
for (HistoricTaskInstance hti : list) {
logger.info("name:{},assignee:{},createTime:{},endTime:{}", hti.getName(), hti.getAssignee(), hti.getCreateTime(), hti.getEndTime());
}
}
}
flowable 底層是 MyBatis,所有 SQL 中參數(shù)的傳遞形式和 MyBatis 一致。
8. 歷史數(shù)據(jù)記錄級別
Flowable 需要記錄哪些歷史數(shù)據(jù),有一個日志級別用來描述這個事情,默認有四種級別:
None: 這個表示不存儲任何歷史信息,好處是流程執(zhí)行的時候效率會比較快,壞處是流程執(zhí)行結(jié)束后,看不到曾經(jīng)執(zhí)行過的流程信息了。
Activity: 這個會存儲所有流程實例和活動實例,在流程實例結(jié)束時,頂級流程實例變量的最新值將復制到歷史變量實例中,不會存儲詳細信息。
Audit: 在 Activity 的基礎(chǔ)上,還會存儲歷史詳細信息,包括權(quán)限信息等。默認的日志記錄級別即次。
Full: 這個是在 Audit 的基礎(chǔ)上,還會存儲變量的變化信息,這會記錄大量的數(shù)據(jù),也會導致流程執(zhí)行變慢。
一共就這四種級別,在 Spring Boot 項目中,如果我們想要配置這個日志記錄的級別,其實非常方便,直接在 application.properties 中進行配置即可,如下:
flowable.history-level=none
配置加了這個配置,我們隨便啟動一個流程,然后去查詢 ACT_HI_ 系列的表,發(fā)現(xiàn)都是空的,沒有數(shù)據(jù)。
如果我們將歷史日志記錄的級別改為 activity,那么就會記錄下來流程信息以及活動信息,但是像執(zhí)行的 Task 這些信息都是沒有的(ACT_HI_TASKINST),包括流程參與者的信息(ACT_HI_IDENTITYLINK)等都不會記錄下來。
如果我們將歷史日志記錄的級別改為 audit,則上面提到的這幾種日志就都會記錄下來。但是 ACT_HI_DETAIL 表還是空的,詳細一個流程變量的變化過程不會被記錄下來。
如果我們將日志記錄級別改為 full,那么將會記錄下更多的信息。ACT_HI_DETAIL 表中會記錄下流程變量的詳細信息。
整個過程我就不給小伙伴們演示了大家可以自行嘗試。