Activiti5入门--流程实例、任务的执行

通过前面几篇文章的学习,我们已经了解了流程定义的部署、查询、删除操作。本篇,我们将学习如何启动流程实例、执行任务。

首先,我们先在项目的src/main/java下新建包cn.demo.processinstance,并创建一个新类ProcessInstanceTest,以便我们学习。在学习新内容之前,我们先部署一个新的流程定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 部署流程定义(zip文件格式)
*/
@Test
public void deploymentProcessDefinitionZip() {
InputStream in = this.getClass().getClassLoader().getResourceAsStream("diagrams/helloworld.zip");
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deploy = processEngine.getRepositoryService() // 与流程定义和部署对象相关的Service
.createDeployment() // 创建一个部署对象
.name("流程定义") // 添加部署的名称
.addZipInputStream(zipInputStream)
.deploy(); // 完成部署
System.out.println("部署ID: " + deploy.getId());
System.out.println("部署名称: " + deploy.getName());
}

执行该代码,输出如下结果

部署ID: 901
部署名称: 流程定义

这样我们流程定义就部署好了

启动流程实例

通过前面的HelloWorld例子,我们知道可以通过调用流程引擎的runTimeService的相关方法启动流程实例,现在我们就开始启动流程实例

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 启动流程实例
*/
@Test
public void startProcessInstance() {
// 流程定义的key
String processDefinitionKey = "helloworld";
ProcessInstance processInstance = processEngine.getRuntimeService() // 与正在执行 的流程实例和执行对象相关的Service
.startProcessInstanceByKey(processDefinitionKey); // 使用流程定义的key启动 实例,key对应helloworld.bpmn文件中Id的属性值,默认是按照最新版本的流程定义启动
System.out.println("流程实例ID: " + processInstance.getId()); // 流程实例Id
System.out.println("流程定义ID: " + processInstance.getProcessDefinitionId()); // 流程定义Id
}

执行上述代码,若一切正常,则输出流程实例ID和流程定义ID

流程实例ID: 1001
流程定义ID: helloworld:4:904

正在执行的执行对象表

若上一段代码成功执行,则表示流程实例已经启动好了。此时,act_ru_execution(正在执行的执行对象表)会新增一条记录。

执行SQL语句select * from act_ru_execution,可以看到已经新增了一条记录

流程实例1

流程实例2

其中,ID_被称为执行对象ID,PROC_INSTID被称为流程实例ID,由ACTID字段的值可知,该流程执行到第一个用户节点-提交申请。显而易见,在该条记录当中,ID_PROC_INSTID的值是相等的。但是,需要注意的是,这两个字段的值并不总是相等,相等有条件的

如果是单例流程(没有分支和聚合),那么流程实例ID和执行对象ID是相同的。

流程实例的历史表

activiti中存在一张流程实例的历史表act_hi_procinst(流程实例的历史表)

执行SQL语句select * from act_hi_procinst,可以看到一条PROC_INSTID 为1001的记录,该记录即为与上一张表记录相对应的记录,其ENDTIME为空,表明该流程实例尚未结束。

对于一个流程来说,流程实例只有一个。执行对象可以存在多个(如果存在分支和聚合)

流程实例历史表1流程实例的历史表2

正在执行的任务表

除了流程实例表,activiti还存在一张act_ru_task(正在执行的任务表),该表用来记录该流程实例正在执行的任务。

执行SQL语句select * from act_ru_task,可以看到,该表存在一条记录,其任务所属人是张三。我们查询任个人任务时所依赖的就是这张表。需要注意的是,只有当节点为任务节点类型的时候,该表中才会存在数据

正在执行的任务表1

正在执行的任务表2

历史任务表

相对地,对应于act_hi_taskinst(历史任务表)activiti存在一张历史任务表act_hi_taskinst(历史任务表)

执行SQL语句select * from act_hi_taskinst,结果如下,和act_hi_taskinst(历史任务表)一样,该表也只有当节点为任务节点类型的时候,才会存在数据。

历史任务表1

历史任务2

可以看到,ID_为1004的记录,其字段ENDTIME为空,表明该任务尚未结束,正在执行。

所有活动节点的历史表

显而易见,无论是act_ru_task(正在执行的任务表)还是act_hi_taskinst(历史任务表),其存储的都是任务节点的数据,这时,我们可能需要一张存储所有活动节点历史数据的表。activiti为我们提供了这样一张表-act_hi_actinst(所有活动节点的历史表)来满足我们的要求。

执行SQL语句select * from act_hi_actinst,可以看到表中存在着该流程实例的startEvent1(开始节点)usertask1(任务节点1)的数据。其中,开始节点的ENDTIME字段值不为空,表示其已经结束,此时执行到userTask1,该任务没有结束,其ENDTIME字段是空值。

所有节点的历史表1

所有节点的历史表2

查询和完成个人任务

查看流程图,我们可以看到存在三个用户任务,现在,我们依次查询并完成它,并观察各表数据变化。

查询和完成张三的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 查询当前人的个人任务
*/
@Test
public void findMyPersonalTask() {
String assignee = "张三";
List<Task> list = processEngine.getTaskService() // 与正在执行的任务管理相关的Service
.createTaskQuery() // 创建任务对象
/*查询条件(部分)*/
.taskAssignee(assignee) // 指定个人任务,指定办理人
// .taskCandidateUser(arg0) 组任务的办理人
// .processDefinitionId(arg0) 流程定义id
// .processInstanceId(arg0) // 流程实例id
// .executionId(arg0) // 执行对象id
/*排序*/
.orderByTaskCreateTime().asc()
/*返回结果集*/
// .singleResult() // 返回唯一结果集
// .count() // 返回结果集数量
// .listPage(arg0, arg1) // 分页查询结果集
.list();
// 存储在act_ru_task表
if (list != null && list.size() > 0) {
for (Task task : list) {
System.out.println("任务ID: " + task.getId());
System.out.println("任务名称: " + task.getName());
System.out.println("任务的创建时间: " + task.getCreateTime());
System.out.println("任务的办理人: " + task.getAssignee());
System.out.println("流程实例ID: " + task.getProcessInstanceId());
System.out.println("执行对象ID: " + task.getExecutionId());
System.out.println("流程定义ID: " + task.getProcessDefinitionId());
System.out.println("#######################################");
}
}
}

由于现在是在usertask1,我们查询张三的个人任务,其结果如下

任务ID: 1004
任务名称: 提交申请
任务的创建时间: Tue Sep 04 22:49:25 CST 2018
任务的办理人: 张三
流程实例ID: 1001
执行对象ID: 1001
流程定义ID: helloworld:4:904

在查询到任务之后,我们可以通过任务ID完成个人任务

1
2
3
4
5
6
7
8
9
/**
* 完成我的任务
*/
@Test
public void completeMyPersonalTask() {
String taskId = "1004";
processEngine.getTaskService().complete(taskId);
System.out.println("完成任务:任务Id:" + taskId);
}

执行结果如下

完成任务:任务Id:1004

完成张三的个人任务后,我们来查询各表数据。

首先,我们执行select * from act_ru_execution,查询正在执行的执行对象表

正在执行的执行对象表3

对比上文,该记录的ACT_ID发生了改变,由usertask1变为了usertask2,此时,流程走到了下一个用户任务。

其次,我们执行select * from act_hi_procinst,查询流程实例的历史表

流程实例的历史表3

由于一个流程只有一个流程实例,其始终只有一条记录,该流程实例还在执行,因此ENDTIME字段值仍然为空

接着,我们执行select * from act_ru_task,查询正在执行的任务表

正在执行的任务3

此时,该记录的ID_发生变化,其NAME_字段也发生变化,ASSIGNEE也从张三变成了李四TASK_DEFKEY的值为usertask2。所以,此时我们可以查询到李四的个人任务,却查询不到张三的个人任务。

然后,我们执行select * from act_hi_taskinst,查询历史任务表

历史任务表3

历史任务表4

可以看到ID_为1004的记录其ENDTIME不为空,已经完成了任务,而ID_为1102,TASK_DEFKEY的值为usertask2的李四任务还没有完成,其ENDTIME为空

最后,我们执行select * from act_hi_actinst,查询所有活动节点的历史表

所有节点的历史表3

所有活动节点的历史表4

可以看到,TASKID为1102的记录其ENDTIME字段值为空,表示任务还未完成,而这正是李四的个人任务

查询和完成李四的任务

通过查询act_ru_task(正在执行的任务表),我们知道,流程已经进行走到李四这了,我们先执行查询个人任务代码,将张三换成李四

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 查询当前人的个人任务
*/
@Test
public void findMyPersonalTask() {
String assignee = "李四";
List<Task> list = processEngine.getTaskService() // 与正在执行的任务管理相关的Service
.createTaskQuery() // 创建任务对象
/*查询条件(部分)*/
.taskAssignee(assignee) // 指定个人任务,指定办理人
// .taskCandidateUser(arg0) 组任务的办理人
// .processDefinitionId(arg0) 流程定义id
// .processInstanceId(arg0) // 流程实例id
// .executionId(arg0) // 执行对象id
/*排序*/
.orderByTaskCreateTime().asc()
/*返回结果集*/
// .singleResult() // 返回唯一结果集
// .count() // 返回结果集数量
// .listPage(arg0, arg1) // 分页查询结果集
.list();
// 存储在act_ru_task表
if (list != null && list.size() > 0) {
for (Task task : list) {
System.out.println("任务ID: " + task.getId());
System.out.println("任务名称: " + task.getName());
System.out.println("任务的创建时间: " + task.getCreateTime());
System.out.println("任务的办理人: " + task.getAssignee());
System.out.println("流程实例ID: " + task.getProcessInstanceId());
System.out.println("执行对象ID: " + task.getExecutionId());
System.out.println("流程定义ID: " + task.getProcessDefinitionId());
System.out.println("#######################################");
}
}
}

运行代码,可以看到如下结果

任务ID: 1102
任务名称: 审批【部门经理】
任务的创建时间: Wed Sep 05 23:48:17 CST 2018
任务的办理人: 李四
流程实例ID: 1001
执行对象ID: 1001
流程定义ID: helloworld:4:904

接下来,我们使用任务ID完成李四的任务

1
2
3
4
5
6
7
8
9
/**
* 完成我的任务
*/
@Test
public void completeMyPersonalTask() {
String taskId = "1102";
processEngine.getTaskService().complete(taskId);
System.out.println("完成任务:任务Id:" + taskId);
}

得到结果

完成任务:任务Id:1102

现在,我们再次看看上面提到的那几张表的表数据

首先是act_ru_execution(正在执行的执行对象表),此时其ACTID的值为usertask3,流程已经进行到王五这了

执行完李四任务的正在执行的执行对象表

其次,我们看看act_hi_procinst(流程实例的历史表),其没有任何变化

执行完李四任务的流程实例的历史表

接着是act_ru_task(正在执行的任务表),其TASK_DEFKEY的值为usertask3,这也表明了流程已经进行到王五这了

执行完李四任务的正在执行的任务表1

执行完李四任务的正在执行的任务表2

然后,我们再看act_hi_taskinst(历史任务表),王五的这条记录

执行完李四任务的历史任务表1

执行完李四任务的历史任务表2

最后,我们看act_hi_actinst(所有活动节点的历史表),王五的这条ENDTIME这条记录的字段值为空,表明王五的任务还未完成。

执行完李四任务的所有活动节点的历史表

执行完李四任务的所有活动节点的历史表2

为此,我们查询并完成王五的任务,走完这个流程

查询和完成王五的任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Test
public void findMyPersonalTask() {
String assignee = "王五";
List<Task> list = processEngine.getTaskService() // 与正在执行的任务管理相关的Service
.createTaskQuery() // 创建任务对象
/*查询条件(部分)*/
.taskAssignee(assignee) // 指定个人任务,指定办理人
// .taskCandidateUser(arg0) 组任务的办理人
// .processDefinitionId(arg0) 流程定义id
// .processInstanceId(arg0) // 流程实例id
// .executionId(arg0) // 执行对象id
/*排序*/
.orderByTaskCreateTime().asc()
/*返回结果集*/
// .singleResult() // 返回唯一结果集
// .count() // 返回结果集数量
// .listPage(arg0, arg1) // 分页查询结果集
.list();
// 存储在act_ru_task表
if (list != null && list.size() > 0) {
for (Task task : list) {
System.out.println("任务ID: " + task.getId());
System.out.println("任务名称: " + task.getName());
System.out.println("任务的创建时间: " + task.getCreateTime());
System.out.println("任务的办理人: " + task.getAssignee());
System.out.println("流程实例ID: " + task.getProcessInstanceId());
System.out.println("执行对象ID: " + task.getExecutionId());
System.out.println("流程定义ID: " + task.getProcessDefinitionId());
System.out.println("#######################################");
}
}
}

运行上述代码,得到如下结果

任务ID: 1202
任务名称: 审批【总经理】
任务的创建时间: Thu Sep 06 19:59:40 CST 2018
任务的办理人: 王五
流程实例ID: 1001
执行对象ID: 1001
流程定义ID: helloworld:4:904

接下来,完成王五的任务

1
2
3
4
5
6
7
8
9
/**
* 完成我的任务
*/
@Test
public void completeMyPersonalTask() {
String taskId = "1202";
processEngine.getTaskService().complete(taskId);
System.out.println("完成任务:任务Id:" + taskId);
}

执行代码,结果如下

完成任务:任务Id:1202

现在,我们再查询一下这些表的数据

首先,先看act_ru_execution(正在执行的执行对象表)

显而易见,由于执行完王五的任务之后,流程已经结束了,因此该表数据为空

再看,act_hi_procinst(流程实例的历史表)ID_1001的这条记录的ENDTIME值不为空,表明该流程实例已经结束。

执行完王五任务的流程实例历史表1

执行完王五任务的流程实例历史表2

接着看act_ru_task(正在执行的任务表)

由于流程实例已经结束,已经没有任务在执行,所以该表数据为空

然后,我们看一下act_hi_taskinst(历史任务表),属于王五的记录其ENDTIME不为空,表明王五完成了任务

执行完王五任务的历史任务表1

执行完王五任务的历史任务表2

最后看一下act_hi_actinst(所有活动节点的历史表),所有属于该流程实例的ENDTIME值不为空,所有活动节点都已经结束了。完成王五任务的所有历史活动节点表1

完成王五任务的所有历史活动节点表2

至此,该流程已经完全结束了。

查询流程状态

有些时候,我们需要判断流程正在执行还是已经结束,则可以通过流程实例ID,调用runTimeService服务方法创建流程实例查询来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 查询流程状态(判断流程正在执行还是结束)
*/
@Test
public void isProcessEnd() {
String processInstanceId = "1001";
ProcessInstance singleResult = processEngine.getRuntimeService() // 表示正在执行的流程实例和执行对象
.createProcessInstanceQuery() // 创建流程实例查询
.processInstanceId(processInstanceId) // 使用流程实例ID查询
.singleResult();
if(singleResult==null) {
System.out.println("流程实例已经结束");
}else {
System.out.println("流程没有结束");
}
}

运行上述代码,结果如下

流程实例已经结束

查询历史任务

当流程结束以后,我们可以查询历史任务,查询历史任务需要用到historyService服务创建历史任务实例查询实现,其代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 查询历史任务
*/
@Test
public void findHistoryTask() {
String taskAssignee = "张三";
List<HistoricTaskInstance> list = processEngine.getHistoryService()
// 与历史数据(历史表相关的数据)
.createHistoricTaskInstanceQuery() // 创建历史的任务实例查询
.taskAssignee(taskAssignee) // 指定历史任务的办理人
.list();
if (list != null && list.size() > 0) {
for (HistoricTaskInstance his : list) {
System.out.println("历史任务id:" + his.getId());
System.out.println("历史任务名称:" + his.getName());
System.out.println("流程实例id:" + his.getProcessInstanceId());
System.out.println("开始时间:" + his.getStartTime());
System.out.println("结束时间:" + his.getEndTime());
System.out.println("持续时间:" + his.getDurationInMillis());
System.out.println("");
}
}
}

运行结果如下

历史任务id:1004
历史任务名称:提交申请
流程实例id:1001
开始时间:Tue Sep 04 22:49:25 CST 2018
结束时间:Wed Sep 05 23:48:17 CST 2018
持续时间:89932060

历史任务id:104
历史任务名称:提交申请
流程实例id:101
开始时间:Mon Jul 02 23:21:44 CST 2018
结束时间:Mon Jul 09 20:05:57 CST 2018
持续时间:593053592

查询历史流程实例

使用historyService不仅可以查询历史任务,还可以查询历史流程实例,并且对于一个流程来说,这个结果是惟一的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 查询历史流程实例
*/
@Test
public void findHistoryProcessInstance() {
String processInstanceId = "1001";
HistoricProcessInstance singleResult = processEngine.getHistoryService()
// 与历史数据(历史表相关的数据)
.createHistoricProcessInstanceQuery() // 创建历史流程实例查询
.processInstanceId(processInstanceId) // 使用流程实例id
.singleResult();
System.out.println("流程实例id:"+singleResult.getId());
System.out.println("流程定义id:"+singleResult.getProcessDefinitionId());
System.out.println("开始时间:"+singleResult.getStartTime());
System.out.println("结束时间:"+singleResult.getEndTime());
System.out.println("持续时间:"+singleResult.getDurationInMillis());
}

运行代码,结果如下

流程实例id:1001
流程定义id:helloworld:4:904
开始时间:Tue Sep 04 22:49:25 CST 2018
结束时间:Thu Sep 06 21:06:56 CST 2018
持续时间:166651989

主要内容总结

执行对象和流程实例基本概念归纳

ProcessInstance(流程实例)

代表流程定义的执行实例 ,一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。

流程实例表示一个流程从开始到结束的最大的流程分支,即一个流程中流程实例只有一个。

Execution(执行对象)

Activiti用这个对象去描述流程执行的每一个节点。在没有并发的情况下,Execution就是ProcessInstance

流程按照流程定义的规则执行一次的过程,就可以表示执行对象Execution。

流程实例源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Licensed under the Apache License, Version 2.0 (the "License");
package org.activiti.engine.runtime;
import java.util.Map;
import org.activiti.engine.repository.ProcessDefinition;
/** Represents one execution of a {@link ProcessDefinition}.
*
* @author Tom Baeyens
* @author Joram Barrez
* @author Daniel Meyer
*/
public interface ProcessInstance extends Execution {
/**
* The id of the process definition of the process instance.
*/
String getProcessDefinitionId();
/**
* The business key of this process instance.
*/
String getBusinessKey();
/**
* returns true if the process instance is suspended
*/
boolean isSuspended();
/** Returns the process variables if requested in the process instance query */
Map<String, Object> getProcessVariables();
}

从源代码中可以看出ProcessInstance就是Execution。但在现实意义上有所区别

单线流程中的流程实例和执行对象

在单线流程中,如上图的贷款流程,ProcessInstanceExecution是一致的。

多线流程中的流程实例和执行对象

这个例子有一个特点:wire-money(汇钱)和archive(存档)是并发执行的。这个时候,总线路代表ProcessInstance,而分线路中每个活动代表Execution

因此,

  1. 一个流程中,执行对象可以存在多个,但是流程实例只能有一个。
  2. 当流程按照规则只执行一次的时候,那么流程实例就是执行对象。

流程实例和执行对象相关表

Execution(执行对象)

对应的表:
act_ru_execution: 正在执行的信息
act_hi_procinst:已经执行完的历史流程实例信息
act_hi_actinst:存放历史所有完成的活动

ProcessInstance(流程实例)

  1. 如果是单例流程,执行对象ID就是流程实例ID 。
  2. 如果一个流程有分支和聚合,那么执行对象ID和流程实例ID就不相同。
  3. 一个流程中,流程实例只有1个,执行对象可以存在多个。

Task(任务)

对应的表:
act_ru_task:正在执行的任务信息
act_hi_taskinst:已经执行完的历史任务信息