Activiti5入门--流程变量

本章开始,我们来了解一个重要的概念-流程变量。流程变量在整个工作流中发挥着极其重要的作用,它记录了流程中的一些重要信息。例如,在请假流程中,请假天数和请假原因等都是流程变量的范围。

流程变量

在上图中就涉及到设置和获取流程变量的问题。对于一个流程实例来说,其可以有多个流程变量,各个流程实例的流程变量是互不影响的。流程实例结束以后,流程变量仍然保存在数据库中。

流程变量流程图

为了更好地讲解流程变量这个概念,我们再画一张流程图,在src\main\resources\diagrams下新建processVariables.bpmn文件,其包含以下节点

  1. 开始节点,点击空白处,选择process选项卡,设置其IdprocessVariables,其NameprocessVariablesProcess
  2. 创建第一个用户任务节点,节点Name提交申请,其Assignee张晓晓
  3. 创建第二个用户任务节点,节点Name经理审批,其Assignee李大大
  4. 结束节点

流程变量流程

流程变量作用

刚才说到流程变量在流程中发挥着极其重要的作用,归纳起来,其主要有三个作用

  1. 用来传递业务参数
  2. 指定连线完成任务(同意和拒绝)
  3. 动态指定任务的办理人

接下来,我们就通过实际例子感受流程变量的使用

流程变量使用示例

部署流程定义

不可避免地,我们先要部署流程定义,启动流程实例。当这些准备工作做好后,才可以使用流程变量

首先,我们先创建一个新包cn.demo.processvariables,再新建一个类ProcessVariablesTest 。不同于上一章使用zip部署流程定义,现在,我们使用输入流来部署流程定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 部署流程定义(从inputstream)
*/
@Test
public void deploymentProcessDefinition() {
InputStream inputstreambpmn = this.getClass().getResourceAsStream("/diagrams/processVariables.bpmn");
InputStream inputstreampng = this.getClass().getResourceAsStream("/diagrams/processVariables.png");
Deployment deploy = processEngine.getRepositoryService()
// 与流程定义和部署对象相关的Service
.createDeployment() // 创建一个部署对象
.name("流程定义") // 添加部署的名称
.addInputStream("processVariables.bpmn", inputstreambpmn)
// 使用资源文件的名称(要求与资源文件的名称要一致)和输入流完成部署
.addInputStream("processVariables.png", inputstreampng)
// 使用资源文件的名称(要求与资源文件的名称要一致)和输入流完成部署
.deploy(); // 完成部署
System.out.println("部署ID: " + deploy.getId());
System.out.println("部署名称: " + deploy.getName());
}

若部署成功,则输出以下结果

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

启动流程实例

部署好流程定义以后,接下来,就是启动流程实例了。查询流程定义表可知,流程定义的keyprocessVariables

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 启动流程实例
*/
@Test
public void startProcessInstance() {
// 流程定义的key
String processDefinitionKey = "processVariables";
ProcessInstance processInstance = processEngine.getRuntimeService()
// 与正在执行的流程实例和执行对象相关的Service
.startProcessInstanceByKey(processDefinitionKey);
System.out.println("流程实例ID: " + processInstance.getId()); // 流程实例Id
System.out.println("流程定义ID: " + processInstance.getProcessDefinitionId());
}

若启动成功,则输出以下结果

流程实例ID: 1501
流程定义ID: processVariables:1:1404

这样,准备工作就做好了。

设置和获取流程变量

在启动流程实例以后,接下来就可以学习如何使用流程变量了。使用流程变量可以分为设置流程变量和获取流程变量两部分。

Activiti中,可以使用两个服务来设置和获取流程变量

  • 与流程实例、执行对象相关的runtimeService
  • 与任务相关的taskService

runtimeServicetaskService都可以用来设置流程变量,其方法大致相同,既可以使用基本数据类型,也可以使用自定义对象类型设置流程变量。

这两个服务设置流程变量的方法大致相同,如下表

  1. runtimeService
函数(设置) 作用
setVariable(executionId, variableName, variableValue);或setVariableLocal(executionId, variableName, variableValue); 表示使用执行对象id和流程变量的名称设置流程变量的值,但是一次只能设置一个值
setVariables(executionId, variables);或setVariablesLocal(executionId, variables); 表示使用执行对象id和map集合设置流程变量,map集合的key就是流程变量的名称,value为流程变量的值
startProcessInstanceByKey(processDefinitionKey, variables); 启动流程实例的同时设置流程变量,使用map集合
函数(获取) 作用
getVariable(executionId, variableName); 使用执行对象的Id和流程变量的名称获取流程变量的值
getVariables(executionId); 使用执行对象的Id获取所有的流程变量,将流程变量放置到Map集合中,Map集合中的key为流程变量的名称,value为流程变量的值
getVariables(executionId, collection); 使用执行对象Id获取流程变量的值,通过设置流程变量的名称存放到一个集合中,来获取指定流程变量名称的流程变量
  1. taskService
函数(设置) 作用
setVariable(taskId, variableName, variableValue);或setVariableLocal(taskId, variableName, variableValue); 表示使用任务id和流程变量的名称设置流程变量的值,但是一次只能设置一个值
setVariables(taskId, variables);或setVariablesLocal(taskId, variables); 表示使用任务id和map集合设置流程变量,map集合的key就是流程变量的名称,value为流程变量的值
complete(taskId, variables); 完成任务的同时设置流程变量
函数(获取) 作用
getVariable(taskId, variableName); 使用任务Id和流程变量的名称获取流程变量的值
getVariables(taskId); 使用任务Id获取所有的流程变量,将流程变量放置到Map集合中,Map集合中的key为流程变量的名称,value为流程变量的值
getVariables(taskId, collection); 使用任务Id获取流程变量的值,通过设置流程变量的名称存放到一个集合中,获取指定流程变量名称的流程变量

使用基本数据类型

由上一节可以知道,当前的任务Id为1504,可以使用taskService设置流程变量

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 设置流程变量
*/
@Test
public void setVariables() {
String taskId = "1504";
TaskService taskService = processEngine.getTaskService();
/*设置流程变量,使用基本类型*/
taskService.setVariableLocal(taskId, "请假天数", 3);// 与任务Id绑定
taskService.setVariable(taskId, "请假日期", new Date());
taskService.setVariable(taskId, "请假原因", "回家探亲");
System.out.println("设置流程变量成功");
}

若执行成功,则控制台输出“设置流程变量成功”。现在,我们来看一下相关表数据

act_ru_variable(正在执行的流程变量表)

执行SQL语句SELECT * FROM act_ru_variable,可以看到有三条记录,分别是“请假原因”、“请假日期”、“请假天数”这三条记录

任务服务设置流程变量1

任务服务设置流程变量2

分析数据可以看到

  • 使用setVariableLocal方法设置的“请假天数”,其TASKID字段的值不为空,”请假天数”和当前任务绑定,而使用setVariable方法设置的“请假日期”和“请假原因”其TASKID字段的值为空
  • 注意到TYPE_字段,Activiti根据设置的流程变量的值自动判断了值所属的类型,并且将值存在了相对应类型的字段中

act_hi_varinst(历史的流程变量表)

除了act_ru_variable(正在执行的流程变量表),相对应的,还存在一张历史表act_hi_varinst(历史的流程变量表)用于存储历史的流程变量

执行SQL语句SELECT * FROM act_hi_varinst查询该表,同样可以获得三条数据

历史的流程变量表1

历史的流程变量表2

现在我们可以获取这些流程变量的值了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取流程变量
*/
@Test
public void getVariables() {
String taskId = "1504";
TaskService taskService = processEngine.getTaskService();
/*获取流程变量,使用基本类型*/
Integer days = (Integer) taskService.getVariable(taskId, "请假天数");
Date date = (Date) taskService.getVariable(taskId, "请假日期");
String reason = (String) taskService.getVariable(taskId, "请假原因");
System.out.println("请假天数:"+days);
System.out.println("请假日期:"+date);
System.out.println("请假原因:"+reason);
}

运行上述代码,输出”请假天数”、”请假日期”、”请假原因”的值

请假天数:3
请假日期:Wed Sep 26 21:41:11 CST 2018
请假原因:回家探亲

接下来,我们来完成这个任务

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

运行代码,输入任务Id

完成任务:任务Id:1504

此时,张晓晓的任务已经完成,流程执行到李大大,即经理审批这一阶段,此时查询act_ru_task(正在执行的任务表),得到当前任务的任务Id为1702,再次获取流程变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 获取流程变量
*/
@Test
public void getVariables() {
String taskId = "1702";
TaskService taskService = processEngine.getTaskService();
/*获取流程变量,使用基本类型*/
Integer days = (Integer) taskService.getVariable(taskId, "请假天数");
Date date = (Date) taskService.getVariable(taskId, "请假日期");
String reason = (String) taskService.getVariable(taskId, "请假原因");
System.out.println("请假天数:"+days);
System.out.println("请假日期:"+date);
System.out.println("请假原因:"+reason);
}

执行代码,可以得到如下结果

请假天数:null
请假日期:Wed Sep 26 21:41:11 CST 2018
请假原因:回家探亲

对比上一个任务执行结果,可以看到请假天数为空值,其原因正是因为我们在设置”请假天数”的时候使用的是taskServicesetVariableLocal方法,该方法将“请假天数”这个流程变量和特定任务绑定在一起造成的。查询act_ru_variable(正在执行的流程变量表),可以看到”请假天数”记录已经不存在了

正在执行的流程变量3

但是,查询act_hi_varinst(历史的流程变量表),可以看到该数据还存在

历史流程变量3

再次执行设置流程变量方法,taskId修改为1702,”请假天数”修改为5

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 设置流程变量
*/
@Test
public void setVariables() {
String taskId = "1702";
TaskService taskService = processEngine.getTaskService();
/*设置流程变量,使用基本类型*/
taskService.setVariableLocal(taskId, "请假天数", 5);// 与任务Id绑定
taskService.setVariable(taskId, "请假日期", new Date());
taskService.setVariable(taskId, "请假原因", "回家探亲,一起吃个饭");
System.out.println("设置流程变量成功");
}

运行上述代码,查询act_ru_variable(正在执行的流程变量表)

执行完张晓晓任务后的流程变量表

此时,数据再次回复为三条,”请假天数”这条记录又出现了,其TASKID为1702,而”请假日期”和”请假原因”的REV_由前面的1变为了2,上一次的记录被覆盖了。

查询act_hi_varinst(历史的流程变量表)

历史流程变量4

历史流程变量5

可以看到,对应于绑定了不同的任务Id的流程变量”请假天数”,其记录各有一条,而对于没有绑定任务Id

流程变量”请假日期”和”请假原因”则分别只有一条记录,旧记录被新纪录所覆盖。

最后,我们结束李大大的任务

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

再次查询act_ru_variable(正在执行的流程变量表),其记录数为0,没有数据,而act_hi_varinst(历史的流程变量表)则和上图一样,仍然是四条数据

使用Java Bean

为了演示使用java Bean设置和获取流程变量功能,在cn.demo.processvariables包下创建一个简单的Java Bean类Person,其有idname两个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.demo.processvariables;
public class Person {
private Integer id; //编号
private String name; //姓名
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

首先,我们重新部署流程定义并启动流程实例,得到其部署Id为2001,其流程实例Id为2101,其流程定义Id为processVariables:2:2004,查询act_ru_task(正在执行的任务表),得到其任务Id为2104,和上面类似,将该Java Bean设置给“人员信息这个流程变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 设置流程变量
*/
@Test
public void setVariables() {
String taskId = "2104";
TaskService taskService = processEngine.getTaskService();
/*设置流程变量,使用基本类型*/
// taskService.setVariableLocal(taskId, "请假天数", 5);// 与任务Id绑定
// taskService.setVariable(taskId, "请假日期", new Date());
// taskService.setVariable(taskId, "请假原因", "回家探亲,一起吃个饭");
/*设置流程变量,使用java bean类型*/
Person p = new Person();
p.setId(10);
p.setName("翠花");
taskService.setVariable(taskId, "人员信息", p);
System.out.println("设置流程变量成功");
}

执行上述代码,发生异常

org.activiti.engine.ActivitiException: couldn’t find a variable type that is able to serialize cn.demo.processvariables.Person@3232a28a
at org.activiti.engine.impl.variable.DefaultVariableTypes.findVariableType(DefaultVariableTypes.java:62)
at org.activiti.engine.impl.persistence.entity.VariableScopeImpl.createVariableInstance(VariableScopeImpl.java:361)……

从异常信息得到提示,该Java Bean必须实现序列化,因此,我们修改Java Bean代码实现序列化接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.demo.processvariables;
import java.io.Serializable;
public class Person implements Serializable {
private Integer id; //编号
private String name; //姓名
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

再次执行设置流程变量代码,输出”设置流程变量成功”。

执行SQL语句SELECT * FROM act_ru_variable,查询act_ru_variable(正在执行的流程变量表),可以看到流程变量的类型为serializable,其它存储流程变量具体值的字段为空。

JavaBean正在执行的流程变量1

JavaBean正在执行的流程变量2

注意到字段BYTEARRAYID,该字段是存储该Java Bean具体字段值的act_ge_bytearray(资源文件表)主键Id。执行SQL语句SELECT * FROM act_ge_bytearray

存储流程变量的资源文件表

可以看到,ID_为2201的记录的BYTES_存储的值即为该Java Bean的各字段的值,不难猜测,ID_为2203的记录的BYTES_字段存储的值就是act_hi_varinst(历史的流程变量表)的该Java Bean的各字段的值。

接下来,我们获取这个Java Bean中的字段值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取流程变量
*/
@Test
public void getVariables() {
String taskId = "2104";
TaskService taskService = processEngine.getTaskService();
/*获取流程变量,使用基本类型*/
// Integer days = (Integer) taskService.getVariable(taskId, "请假天数");
// Date date = (Date) taskService.getVariable(taskId, "请假日期");
// String reason = (String) taskService.getVariable(taskId, "请假原因");
// System.out.println("请假天数:"+days);
// System.out.println("请假日期:"+date);
// System.out.println("请假原因:"+reason);
/*获取流程变量,使用javabean*/
Person p = (Person) taskService.getVariable(taskId, "人员信息");
System.out.println(p.getId()+","+p.getName());
}

运行代码,输出如下结果

10,翠花

注意,当一个Java Bean(实现序列化)放置到流程变量后,要求Java Bean的属性不能再发生变化,如果发生变化,再次获取的时候,将会抛出异常。

现在,我们将修改Person类代码,增加一个属性

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
package cn.demo.processvariables;
import java.io.Serializable;
public class Person implements Serializable {
private Integer id; //编号
private String name; //姓名
private String education;
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

再次执行获取流程变量方法,发生异常

org.activiti.engine.ActivitiException: Couldn’t deserialize object in variable ‘人员信息’
at org.activiti.engine.impl.variable.SerializableType.getValue(SerializableType.java:68)
at org.activiti.engine.impl.persistence.entity.VariableInstanceEntity.getValue(VariableInstanceEntity.java:165)……

为了解决该问题,我们先将Person类恢复到原状,为该类指定一个固定的序列号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.demo.processvariables;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = -1934812997221186394L;
private Integer id; //编号
private String name; //姓名
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

紧接着,我们再重新设置流程变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 设置流程变量
*/
@Test
public void setVariables() {
String taskId = "2104";
TaskService taskService = processEngine.getTaskService();
/*设置流程变量,使用基本类型*/
// taskService.setVariableLocal(taskId, "请假天数", 5);// 与任务Id绑定
// taskService.setVariable(taskId, "请假日期", new Date());
// taskService.setVariable(taskId, "请假原因", "回家探亲,一起吃个饭");
/*设置流程变量,使用java bean类型*/
Person p = new Person();
p.setId(20);
p.setName("翠花");
taskService.setVariable(taskId, "人员信息(添加固定版本)", p);
System.out.println("设置流程变量成功");
}

此时,设置流程变量成功,再次执行获取流程变量,成功获取流程变量的值

20,翠花

然后,我们再次恢复字段education,并保持版本号不变

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
package cn.demo.processvariables;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = -1934812997221186394L;
private Integer id; //编号
private String name; //姓名
private String education;
public String getEducation() {
return education;
}
public void setEducation(String education) {
this.education = education;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

再次执行获取流程变量方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取流程变量
*/
@Test
public void getVariables() {
String taskId = "2104";
TaskService taskService = processEngine.getTaskService();
/*获取流程变量,使用基本类型*/
// Integer days = (Integer) taskService.getVariable(taskId, "请假天数");
// Date date = (Date) taskService.getVariable(taskId, "请假日期");
// String reason = (String) taskService.getVariable(taskId, "请假原因");
// System.out.println("请假天数:"+days);
// System.out.println("请假日期:"+date);
// System.out.println("请假原因:"+reason);
/*获取流程变量,使用javabean*/
Person p = (Person) taskService.getVariable(taskId, "人员信息(添加固定版本)");
System.out.println(p.getId()+","+p.getName());
}

此时程序不再报异常,能够获取到正确的值。

因此,我们若希望能够当Java Bean的属性发生变化并再次获取时不发生异常,实现序列化接口的同时,固定Java Bean的序列号

为了便于查询历史流程变量,我们先完成流程

完成张晓晓的任务

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

完成李大大的任务

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

查询流程变量历史表

最后,我们查询一下流程变量历史表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 查询流程变量历史表
*/
@Test
public void findHistoryProcessVariables() {
List<HistoricVariableInstance> list = processEngine.getHistoryService()
.createHistoricVariableInstanceQuery()
.variableName("请假天数")
.list();
if(list!=null && list.size()>0) {
for(HistoricVariableInstance hvi:list) {
System.out.println(hvi.getId()+","
+hvi.getProcessInstanceId()
+","+hvi.getVariableName()
+","+hvi.getVariableTypeName()
+","+hvi.getValue());
System.out.println();
}
}
}

运行代码,输出结果

1601,1501,请假天数,integer,3

1801,1501,请假天数,integer,5

总结

流程变量主要知识点

  1. 流程变量的作用
    • 在流程执行或者任务执行的过程中,用于设置和获取变量
    • 使用流程变量在流程传递的过程中传递业务参数
  2. 流程变量对应的表
    • act_ru_variable:正在执行的流程变量表
    • act_hi_varinst:流程变量历史表
  3. 设置流程变量的设置
    • 流程变量的作用域就是流程实例,所以可以在任何阶段设置
    • 使用基本类型设置流程变量,在taskService中使用任务Id,定义流程变量的名称,设置流程变量的值
    • 使用Javabean类型设置流程变量,需要这个JavaBean实现了Serializable接口
    • 设置流程变量的时候,意味着向act_ru_variable这个表添加数据
  4. 获取流程变量
    • 流程变量的获取针对流程实例(即1个流程),每个流程实例获取的流程变量是不同的
    • 使用基本类型获取流程变量,在taskService中使用任务ID,流程变量的名称,获取流程变量的值
    • JavaBean类型设置和获取流程变量,除了需要这个JavaBean实现了Serializable接口外,还要求流程变量对象的属性不能发生变化,否则抛出异常。解决方案是固定该JavaBean的序列化Id
  5. 获取和设置流程变量的服务和时机以及方式
    • RuntimeService对象可以设置流程变量和获取流程变量
    • TaskService对象可以设置流程变量和获取流程变量
    • 流程实例启动的时候可以设置流程变量
    • 任务办理完成的时候可以设置流程变量
    • 流程变量可以通过名称/值的形式设置单个流程变量
    • 流程变量可以通过Map集合,同时设置多个流程变量,Map集合的key表示流程变量的名称,Map集合的value表示流程变量的值

setVariable和setVariableLocal的区别

setVariable:

设置流程变量的时候,流程变量名称相同的时候,后一次的值替换前一次的值,而且可以看到TASK_ID的字段不会存放任务ID的值

setVariableLocal:

  1. 设置流程变量的时候,针对当前活动的节点设置流程变量,如果一个流程中存在2个活动节点,对每个活动节点都设置流程变量,即使流程变量的名称相同,后一次的版本的值也不会替换前一次版本的值,它会使用不同的任务ID作为标识,存放2个流程变量值,而且可以看到TASK_ID的字段会存放任务ID的值
  2. 还有,使用setVariableLocal说明流程变量绑定了当前的任务,当流程继续执行时,下个任务获取不到这个流程变量(因为正在执行的流程变量中没有这个数据),所有查询正在执行的任务时不能查询到我们需要的数据,此时需要查询历史的流程变量