1、流程引擎的创建。

1.1、ProcessEngineConfiguration的buildProcessEngine方法

使用ProcessEngineConfiguration的create方法可以得到ProcessEngineConfiguration的实例。ProcessEngineConfiguration中提供了一个buildProcessEngine方法,该方法返回一个ProcessEngine实例。方法如下:

//读取配置ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("build_engine.xml");//创建ProcessEngineProcessEngine engine = config.buildProcessEngine();

得到流程引擎的相关配置后,buildProcessEngine方法会根据这些配置,初始化流程引擎的相关服务和对象,包括数据源、事务、拦截器、服务组件等等。这个流程引擎的初始化过程,实际上也可以看作是一个配置检查的过程。

1.2、ProcessEngines对象

除了ProcessEngineConfiguration的buildProcessEngine方法外,ProcessEngines类提供了创建ProcessEngineConfiguration实例的方法。ProcessEngines是一个创建流程引擎与关闭流程引擎的工具类,所有创建(包括其他方式创建)的ProcessEngine实例均被注册到ProcessEnegines实例中。这里所说的注册,实际上是在ProcessEngines类中维护一个Map对象,该对象的key为ProcessEngine实例的名称,value为ProcessEngine的实例,当向ProcessEngines注册ProcessEngine实例时,实际上是调用Map的put方法,将该实例缓存到Map中。

1.2.1、init方法与getDefaultProcessEngine方法

ProcessEngines的init方法,会读取Activiti的默认配置文件,然后将创建的ProcessEngine实例缓存到Map中。这里所说的默认配置文件,一般情况下是指ClassPath下的activiti.cfg.xml,如果与Spring进行了整合,则读取ClassPath下的activiti-context.xml文件。调用ProcessEnginesd init方法示例:

//初始化ProcessEngines的Map,//再加载Activiti默认的配置文件(classpath下的activiti.cfg.xml文件)//如果与Spring整合,则读取classpath下的activiti-context.xml文件ProcessEngines.init();//得到ProcessEngines的MapMap<String, ProcessEngine> engines= ProcessEngines.getProcessEngines();System.out.println(engines.size());System.out.println(engines.get("default"));

调用了init方法后,Activiti会根据默认配置创建ProcessEngine实例,此时Map的key值为“default”,以上代码输出如下:

1org.activiti.engine.impl.ProcessEngineImpl@a23610

此处的init方法并不会返回任何的ProcessEngine实例,该方法只会加载ClassPath下全部的activiti配置文件并且将创建的ProcessEngine实例保存到ProcessEngines中。如果需要得到相应的ProcessEngine实例,可以使用getProcessEngines方法获取ProcessEngines中全部的ProcessEngine实例。getProcessEngines返回一个Map,只需要根据ProcessEngine的名称,即可得到相应的ProcessEngine实例。

另外,ProcessEngines提供了一个getDefaultProcessEngine方法,用于返回key为“default”的ProcessEngine实例,该方法会判断ProcessEngines是否进行了初始化,如果没有,则会调用init方法进行初始化。

1.2.2、registerProcessEngine方法和unregister方法

registerProcessEngine方法向ProcessEngines中注册一个ProcessEngine实例,unregister方法则注销ProcessEngines中一个ProcessEngine实例。注册与注销ProcessEngine实例,均会根据ProcessEngine实例的名称进行操作,因为Mapd key使用的是ProcessEngine的名称。可以使用如下代码进行读取自定义配置,然后创建ProcessEngine实例,并注册到ProcessEngines中,最后调用unregister方法注销该实例。

//读取自定义配置
ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource ("register.xml");
//创建 ProcessEngine 实例
ProcessEngine engine= config.buildProcessEngine();
//获取 ProcessEngine 的 Map
Map<String, ProcessEngine> engines= ProcessEngines.getProcessEngines();
System.out.println ("注册后引擎数:"+ engines.size());//注销 ProcessEngine 实例
ProcessEngines.unregister(engine);
System.out.println ("调用 unregister 后引擎数:"+ engines.size());

在上述代码中,使用ProcessEngineConfiguration的buildProcessEngine方法,即会将创建的ProcessEngine实例注册到ProcessEngines中,并不需要再次调用registerProcessEngine方法。调用了ProcessEngineConfiguration的buildProcessEngine方法后,可以获取ProcessEngines的Map,输出ProcessEngine的实例数,最后调用注销方法,再输出实例数。运行代码结果如下:

注册后引擎数:1
调用unregister后引擎数:0

默认情况下,创建的ProcessEngine名称为“default”,如果需要设置名称,则可调用引擎配置类的setProcessEngineName方法。ProcessEngines中的维护的Map对象,其key值就是引擎的名称。

注意:unregister方法只是单纯地将ProcessEngine实例从Map中移除,并不会调用ProcessEngine的close方法。

1.2.3、retry方法

如果Activiti在加载配置文件时出现异常,可以调用ProcessEngines的retry方法重新加载配置文件,重新创建ProcessEngine实例并加入到Map中。如下代码使用retry方法加载配置文件:

//得到资源文件的URL实例ClassLoader cl = Retry.class.getClassLoader();URL url = cl.getResource("retry.xml");//调用retry方法创建ProcessEngine实例ProcessEngineInfo info = ProcessEngines.retry(url.toString());//得到流程实例保存对象Map<String, ProcessEngine> engines = ProcessEngines.getProcessEngines();System.out.println("调用retry方法后引擎数:" + engines.size());

1.2.4、destroy方法

ProcessEngines的destroy方法,顾名思义,是对其所有维护的ProcessEngine实例进行销毁,并且在销毁时,会调用全部ProcessEngine实例的close方法。以下代码使用destory方法:

//进行初始化并且返回默认的ProcessEngine实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();System.out.println("调用getDefaultProcessEngine方法后引擎数量:"+ ProcessEngines.getProcessEngines().size());//调用销毁方法ProcessEngines.destroy();//最终结果为0System.out.println("调用destroy方法后引擎数量:"+ ProcessEngines.getProcessEngines().size());//得到资源文件的URL实例ClassLoader cl = Destroy.class.getClassLoader();URL url = cl.getResource("activiti.cfg.xml");//调用retry方法创建ProcessEngine实例ProcessEngines.retry(url.toString());System.out.println("只调用retry方法后引擎数量:"+ ProcessEngines.getProcessEngines().size());//调用销毁方法,没有效果ProcessEngines.destroy();System.out.println("调用destory无效果,引擎数量:"+ ProcessEngines.getProcessEngines().size());

ProcessEngine实例销毁的前提是ProcessEngines的初始化状态为true,如果为false,则调用destory不会有效果。例如调用retry方法,再调用destory方法,则不会有销售效果,因为retry方法并没有设置初始化状态。代码输出结果:

调用getDefaultProcessEngine方法后引擎数量:1调用destroy方法后引擎数量:0只调用 retry方法后引擎数量:1调用destory无效果,引擎数量:1

在destory执行时,会调用全部ProcessEngine实例close方法,该方法会将异步执行器(AsyncExecutor)关闭,如果流程引擎配置的数据库策略为create-drop,则会执行数据表的删除操作。

1.3、ProcessEngine对象

1.3.1、服务组件

在Activiti中,一个ProcessEngine实例代表一个流程引擎,ProcessEngine中保存着各个服务组件的实例,根据这些服务组件,可以操作流程实例、任务、系统角色等数据。

当创建了流程引擎实例后,在ProcessEngine中会初始化一系列服务组件,这些组件提供了大部分操作流程引擎数据的业务方法,可以使用ProcessEngine中的getXXXService方法得到这些组件的实例。一个ProcessEnegine主要有以下实例:

RepositoryService:提供一系列管理流程定义和流程部署的API。

RuntimeService:在流程运行时对流程实例进行管理与控制。

TaskService:对流程任务进行管理,例如任务提醒、任务完成和创建任务等。

IdentityService:提供对流程角色数据进行管理的API,这些角色数据包括用户组、用户、及它们之间的关系。

ManagementService:提供对流程引擎进行管理和维护的服务。

HistoryService:对流程的历史数据进行操作,包括查询、删除这些历史数据。

DynamicBpmnService:使用该服务,可以不需要重新部署流程模型,就可以实现对流程模型的部分修改。

1.3.2、关闭流程引擎

根据前面信息可知,ProcessEngines实例在销毁时,会调用全部ProcessEngine的close方法,会对流程引擎进行关闭操作,这些操作包括关闭异步执行器(AsyncExecutor)和执行数据库表删除(drop),需要让其删除数据表,前提是要将流程引擎配置的databaseSchemaUpdate属性设置为create-drop。以下代码为执行close方法,运行前请将全部数据表删除:

//读取配置ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("close.xml");//创建流程引擎ProcessEngine engine = config.buildProcessEngine();System.out.println("完成流程引擎创建");Thread.sleep(10000);//执行close方法engine.close();

注意代码清单使用了Thread.sleep方法,可以利用暂停的这10秒钟时间去查看数据库的表是否已经创建成功,10秒后执行close方法,以下为代码清单5-7输出的日志信息:

22:21:43,348  INFO DbSqlSession - performing create on engine with resource org/activiti/db/create/activiti.mysql.create.engine.sql

22:21:43,348  INFO DbSqlSession - Found MySQL: majorVersion=5 minorVersion=6

22:21:43,355  INFO DefaultManagementAgent - JMX Connector thread started and listening at: service:jmx:rmi:///jndi/rmi://AY-PC:1099/jmxrmi/activiti

22:22:48,881  INFO DbSqlSession - performing create on history with resource org/activiti/db/create/activiti.mysql.create.history.sql

22:22:48,882  INFO DbSqlSession - Found MySQL: majorVersion=5 minorVersion=6

22:23:04,069  INFO DbSqlSession - performing create on identity with resource org/activiti/db/create/activiti.mysql.create.identity.sql

22:23:04,070  INFO DbSqlSession - Found MySQL: majorVersion=5 minorVersion=6

22:23:08,724  INFO ProcessEngineImpl - ProcessEngine default created

完成流程引擎创建

22:23:18,948  INFO DbSqlSession - performing drop on engine with resource org/activiti/db/drop/activiti.mysql.drop.engine.sql

22:23:18,965  INFO DbSqlSession - Found MySQL: majorVersion=5 minorVersion=6

22:23:33,613  INFO DbSqlSession - performing drop on history with resource org/activiti/db/drop/activiti.mysql.drop.history.sql

22:23:33,613  INFO DbSqlSession - Found MySQL: majorVersion=5 minorVersion=6

22:23:42,030  INFO DbSqlSession - performing drop on identity with resource org/activiti/db/drop/activiti.mysql.drop.identity.sql

22:23:42,030  INFO DbSqlSession - Found MySQL: majorVersion=5 minorVersion=6

注意以上输出信息的粗体部分,完成了流程引擎创建后,由于调用了close方法,并且databaseSchemaUpdate属性设置为create-drop,因此Activiti会执行相应数据库的drop脚本。

注:运行代码清单前,需要先将数据库中的全部数据表删除。

1.3.3、流程引擎名称

根据前面的介绍可以知道, 每个ProcessEngine实例均有自己的名称,在ProcessEngines的Map中,会使用该名称作为Map的key值,如果不为ProcessEngine设置名称的话,Activiti会默认的将其设置为“default”。ProcessEngine本身没有提供设置名称的方法,该方法由ProcessEngineConfiguration提供。以下代码为ProcessEngine设置名称。

ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("name.xml");//设置流程引擎名称config.setProcessEngineName("test");//**ProcessEngine engine = config.buildProcessEngine();//根据名称查询流程引擎ProcessEngine engineTest = ProcessEngines.getProcessEngine("test");System.out.println("创建的引擎实例:" + engine);System.out.println("查询的引擎实例:" + engineTest);

代码中的加**字代码,调用了ProcessEngineConfiguration的setProcessEngineName方法将流程引擎名称设置为test,然后根据该名称到ProcessEngines中查询相应的流程引擎,代码输出结果两个引擎均为同一对象。由此可知,buildProcessEngine方法实际上完成了ProcessEngines的register操作,运行代码输出结果如下:

创建的引擎实例:org.activiti.engine.impl.ProcessEngineImpl@16fe72b
查询的引擎实例:org.activiti.engine.impl.ProcessEngineImpl@16fe72b

1.4、本章小结

本章主要讲述了如何利用 Activiti 的配置创建流程引擎对象(ProcessEngine)。 介绍了创建 流程引擎对象的两种方法 : ProcessEngineConfiguration 的 buildProcessEngine 方 法和 ProcessEngines 的 init 方法。 除此之外,如果项目中使用了 Spring,还可以将 ProcessEngine 作 为一个 bean 配置到 XML 文件中,然后使用 Spring 的 API 获取。

本章内容较为简单, 掌握 ProcessEngines 对象的使用与 ProcessEngine 对象的使用就可以 了,本章中一些关于实现原理的描述,如果不感兴趣可不必掌握。

2、用户组与用户

在任何的业务场景中,人都是重要的业务参与者,因此各个工作流技术,都会涉及用户组与用户的管理(可能某些技术不会称为用户组和用户,但也会存在相同的概念),Activiti也不例外。

作为流程中的基础数据,Activiti提供了一套控制用户组与用户的API,通过这些API,可以对流程的基础数据进行管理。本部分将详细讲解Activiti中用户组和用户的设计,并且使用这些API来完成工作。只要讲到流程就有可能会使用用户组及用户数据,因此,笔者杨大仙将它们放到服务组件的前面讲解,在Activiti中,管理这些数据的服务组件为IdentityService。

2.1、用户组管理

某些企业会将相同职能的人作为一类人进行管理,也会有某些企业,将相同的角色(职能不同)作为一类人进行管理,这些类别就是本章所说的用户组。 Activiti 中用户组对应的表为 ACT_ID_GROUP,该表只有 4 个宇段。 本节讲述用户组的增加、修改和删除操作。 用户组数据的查询将结合 Activiti 的数据查询在 2.2 中讲解。

2.1.1、Group 对象

Group 是一个接口, 一个 Group 实例表示一条用户组数据,对应的表为 ACT_ID_GROUP。 该接口只提供了相应宇段的 getter和 setter 方法,这种数据与对象的映射关系,在各个 ORM 框架中使用很普遍,例如Hibernate、MyBatis等。

在 Activiti 中,每个实体会有自己的接口与实现类,以 Group 接口为例,有一个 GroupEntity 的子接口,还有一个 GroupEntitylmpl 的实现类。在 Activiti 的 API 文档中,并没有开放 GroupEntity 与 GroupEntitylmpl 的实现类, 创建 Group 实例, 要调用业务组件方法。 Activiti 不希 望使用者去关心这些实体的创建,这样做的好处是,即使 Activiti 在以后版本中发生设计的变 化,也可以减少对使用者的影响。 GroupEntity 中包含以下映射属性。

id: 主键,对应 ACT_ID_GROUP 表的 ID列。

name: 用户组名称,对应 ACT_ID_GROUP 表的 NAME 列。

type:用户组类型,对应 ACT_ID_GROUP 表的 TYPE_列。

revision: 该用户组数据的版本,对应 ACT_ID_GROUP 表的 REV_列。

2.1.2、创建用户组

ldentityService 提供了 newGroup 方法来创建 Group 实例,得到由 IdentityService 创建的实例后,可以调用相应的 setter 方法设置相应字段,最后调用 saveGroup 方法,将 Group 保存到 数据库中。 代码清单 2-1 为一个使用 newGroup 和 saveGroup 方法的示例。

//创建默认的流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService= engine.getidentityService();
//生成 UUID
String genId = UUID.randomUUID () . toString ();
//调用 newGroup 方法创建 Group 实例
Group group= identityService.newGroup(genid);
group.setName("经理组");
group.setType ("manager");//保存 Group 到数据库
identityService.saveGroup(group);//查询用户组
Group_data =identityService.createGroupQuery().groupid(genid).singleResult();// 保存后查询
Group_System.out.println("Group_ID:" + data.getid() + ", Name :"+ data .getName() ); 

需要注意的是,调用 newGroup 方法需要提供 Group 的 ID 值(代码中使用的UUID), 如果将D值设置为null,则调用 newGroup方法时会抛出 ActivitiException, 异常信息为: groupId is null。

IdentityService 的 saveGroup 方法会判断 Group 的 revision 属性(对应 ACT_ID_GROUP 表的 REV_字段)是否为 0,版本号为 0 则做新增处理,反之则做修改处理。 Group 接口并不提供设置与获取 revision 的方法,因此该属性的变化,我们不需要关心。 调用 newGroup 时, 需要指定 ID值,由于ID_字段是 ACT_ID_GROUP 表的主键,因此如果让使用者来设定 ID值,可能会出现 ID重复的问题,代码清单 中使用 UUID 则避免了 该问题。 向数据库新建一条主键重复的数据, MySQL 将会产生以下错误信息: Duplicate entry ’1’ for key ’PRIMARY’。 解决主键重复的问题,可以让 Activiti 生成主键,但不提供主键, newGroup 方法又会抛出异常,以下代码可以解决该问题:

//调用newGroup方法创建Group实例
Group group = identityService.newGroup (genid) ;
group.setName( "经理组");
group.setType("manager");
group.setid(null);
//保存 Group 到数据库
identityService.saveGroup(group) ; 

以上代码在调用 newGroup 方法后,又将 id 设为 null,这样做就可以让 Activiti 来生成主键。

2.1.3、修改用户组

修改用户组,同样调用 saveGroup 方法,只需要在调用该方法前,将相应的用户组对象查询出来,然后设置相应字段的值即可。第二次调用 saveGroup 方法时,会判断 Group 的REV_ 值,如果该值不为 0,会被视作修改操作。修改 Group 名称的示例:

Group data = identityService.createGroupQuery().groupid("l").singleResult();
data.setName("经理 2 组");
identityService.saveGroup(data); 

修改了 Group 对象再执行保存后,查看数据库,可以看到REV_字段值变为 2,换言之,在执行update时,Activiti同样会将该值加1。

注意:查询出Group对象后,如果调用 setld方法修改数据的主键值,那么 saveGroup 方法在执行时将查询不到相应的数据,抛出空指针异常。

2.1.4、删除用户组

IdentityService 提供了一个 deleteGroup 方法用于删除用户组数据,用户与用户组数据属于Activiti中的基础数据,这些数据会被流程中的各类数据引用 (一般使用ID列作为外键关联),此时要删除这些基础数据,可以使用以下两种设计方案。

第一种方案:做外键关联,所有使用到用户或者用户组的地方,使用相应的字段做外键关联,该外键指向用户组或者用户表的ID 列。

第二种方案: 不做外键关联,除了身份等模块外,其他使用到用户组或者用户数据的模块,同样提供一个字段来保存用户组或用户数据的外键,但该字段不关联任何表。

采用第一种方案,在删除基础数据时,会导致无法删除或者级联删除,这种方式会加强模块间的耦合。采用第二种方案,其他使用基础数据的模块中,都要考虑这些引用的数据ID是 否己经被删除。 Activiti 各模块间的数据引用,采用的是第二种方案,与用户组关联的数据表 (例如 ACT_RU_IDENTITYLINK 表),仅仅提供一个字段来记录用户组 id,没有做外键关联。

Activiti 的用户组与用户的关系表,就做了外键关联。这里所说的关系表,是用于保存用户组和用户关系的表(ACT_ID_ MEMBERSHIP)。由于一个用户有可能属于多个用户组, 一 个用户组下会有多个用户,对于这种多对多的关系,很多系统都采用中间表的方式来记录它们之间的关系。由于用户组与用户的关系表做了用户组的外键关联,因此删除用户组的 deleteGroup 方法在执行时,会先将这些关联数据删除,然后再删除用户组数据。调用 deleteGroup 方法的示例:

//创建默认的流程引擎
ProcessEngine engine= ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService();
String genId = UUID.randomUUID().toString ();
//调用newGroup 方法创建 Group 实例
Group group = identityService.newGroup(genId);
group.setName("经理组");
group.setType("manager");
//保存 Group 到数据库
identityService.saveGroup(group);
//查询用户组
System.out.println("保存后用户组数量:" + identityService.createGroupQuery().count());
//根据 ID 删除用户组
identityService.deleteGroup(genid);
System.out.println (”删除后用户组数量:” + identityService.createGroupQuery() .count()); 

输出结果如下:

保存后用户组数量: 1

删除后用户组数量:0

2.2、Activiti数据查询

Activiti提供了一套数据查询API 供开发者使用,可以使用各个服务组件的 create:XXXQuery 方法来获取这些查询对象。 本节将结合用户组数据来讲解 Activiti 的数据查询设计,这些设计可应用于整个 Activiti 的数据查询体系。

2.2.1、查询对象

Activiti的各个服务组件(XXXService)均提供了createXXXQuery方法,例如本章的IdentityService中的createGroupQuery方法和createUserQuery方法,TaskService中的craeteTaskQuery方法等,这些方法返回的是一个Query实例,例如createGroupQuery返回的是GroupQuery,GroupQuery是Query的子接口。

Query是全部查询对象的父接口,该接口定义了若干个基础方法,各个查询对象均可以使用这些公共方法,包括设置排序方式、数据量统计(count)、列表、分页和唯一记录查询。这些方法描述如下:

asc:设置查询结果的排序方式为升序。

count:计算查询结果的数据量。

desc:设置查询结果的排序方式为降序。

list:封装查询结果,返回相应类型的集合。

listPage:分页返回查询结果。

singleResult:查询单条符合条件的数据,如果查询不到,则返回null,如果查询到多条记录,则抛异常。

6.2.2、list方法

Query接口的list方法,将查询对象对应的实体数据以集合形式返回,返回的集合需要指定元素类型,如果没有查询条件,则会将表中全部的数据查出,默认按照主键(ID_列)升序排序。list方法:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, "1", "GroupA", "typeA");createGroup(identityService, "2", "GroupB", "typeB");createGroup(identityService, "3", "GroupC", "typeC");createGroup(identityService, "4", "GroupD", "typeD");createGroup(identityService, "5", "GroupE", "typeE");//使用list方法查询全部的部署数据List<Group> datas = identityService.createGroupQuery().list();//**for (Group data : datas) {System.out.println(data.getId() + "---" + data.getName() + " ");}}//将用户组数据保存到数据库中static void createGroup(IdentityService identityService, String id,String name, String type) {//调用newGroup方法创建Group实例Group group = identityService.newGroup(id);group.setName(name);group.setType(type);identityService.saveGroup(group);}

在代码中,先往数据库中写入5条用户组数据,然后调用Query的list方法将全部数据查出(代码中的**号字代码),需要注意的是,在不设置任何排序条件以及排序方式的情况下,将会以主键升序的方式返回结果,代码运行结果如下:

1---GroupA2---GroupB3---GroupC4---GroupD5---GroupE

2.2.3、listPage方法

listPage方法与list方法类似,最终也是以主键升序排序返回结果集,与list方法不一样的是,listPage方法需要提供两个int参数,第一个参数数据的开始索引,从0开始,第二个参数为结果数量,不难看出,该方法适用于分页查询。listPage方法进行查询:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, "1", "GroupA", "typeA");createGroup(identityService, "2", "GroupB", "typeB");createGroup(identityService, "3", "GroupC", "typeC");createGroup(identityService, "4", "GroupD", "typeD");createGroup(identityService, "5", "GroupE", "typeE");//调用listPage方法,从索引为2的记录开始,查询3条记录List<Group> datas = identityService.createGroupQuery().listPage(2, 3);for (Group data : datas) {System.out.println(data.getId() + "---" + data.getName() + " ");}}//将用户组数据保存到数据库中static void createGroup(IdentityService identityService, String id,String name, String type) {//调用newGroup方法创建Group实例Group group = identityService.newGroup(id);group.setName(name);group.setType(type);identityService.saveGroup(group);}

代码中,使用了listPage方法,查询用户组的数据,设置从第二条记录开始,查询3条记录,该方法与MySQL的LIMIT关键字类似。代码运行结果如下:

3---GroupC4---GroupD5---GroupE

2.2.4、count方法

该方法用于计算查询结果的数据量,类似于SQL中的SELECT COUNT语句,如果不加任何的条件,将会统计整个表的数据量。count方法如下:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, UUID.randomUUID().toString(), "GroupA", "typeA");createGroup(identityService, UUID.randomUUID().toString(), "GroupB", "typeB");createGroup(identityService, UUID.randomUUID().toString(), "GroupC", "typeC");createGroup(identityService, UUID.randomUUID().toString(), "GroupD", "typeD");createGroup(identityService, UUID.randomUUID().toString(), "GroupE", "typeE");//使用list方法查询全部的部署数据long size = identityService.createGroupQuery().count();System.out.println("Group数量:" + size);}
//createGroup方法多次重复,可往前代码查询

2.2.5、排序方法

Query中提供了asc和desc方法,这两个方法可以设置查询结果的排序方式,但是调用这两个方法的前提是,必须告诉Query对象,是按何种条件进行排序,例如要按照ID排序,就要调用相应查询对象的orderByXXX方法。例如GroupQuery的orderByGroupId、orderByGroupName等方法,如果不调用这些方法而直接使用asc或者desc方法,则会抛出ActivitiException,异常信息为:You should call any of the orderBy methods first before specifying a direction。要求Activiti进行排序,却不告诉它以哪个字段进行排序,因此会抛出该异常。代码调用asc和desc方法示例:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, UUID.randomUUID().toString(), "1", "typeA");createGroup(identityService, UUID.randomUUID().toString(), "2", "typeB");createGroup(identityService, UUID.randomUUID().toString(), "3", "typeC");createGroup(identityService, UUID.randomUUID().toString(), "4", "typeD");createGroup(identityService, UUID.randomUUID().toString(), "5", "typeE");//调用orderByGroupId和asc方法,结果为按照ID升序排序System.out.println("asc排序结果:");List<Group> datas = identityService.createGroupQuery().orderByGroupName().asc().list();for (Group data : datas) {System.out.println("    " + data.getId() + "---" + data.getName());}System.out.println("desc排序结果");//调用orderByGroupName和desc方法,结果为名称降序排序datas = identityService.createGroupQuery().orderByGroupName().desc().list();for (Group data : datas) {System.out.println("    " + data.getId() + "---" + data.getName());}}
//createGroup方法往前找

asc排序结果:

35987ec6-de7f-4d36-920f-71d27b586817---1

3273d754-a77f-4a7b-ac88-b529cc5e3d35---2

590f5597-d662-4c35-a35c-c2828468878d---3

f8decda9-ceb9-4172-ad61-ae3d2a8a4e8e---4

0f50f928-a7ff-4b77-b4fd-578773c0fb2f---5

desc排序结果

0f50f928-a7ff-4b77-b4fd-578773c0fb2f---5

f8decda9-ceb9-4172-ad61-ae3d2a8a4e8e---4

590f5597-d662-4c35-a35c-c2828468878d---3

3273d754-a77f-4a7b-ac88-b529cc5e3d35---2

35987ec6-de7f-4d36-920f-71d27b586817---1

注意:调用asc或者desc,只是让Query设置排序方式,orderByXXX方法、asc方法和desc方法均返回Query本身,如果需要得到最终结果集,还需要调用list或者listPage方法。

2.2.6、ID排序问题

在Activiti的设计中,每个数据表的主键均设计为字符型,这样的设计使得Activiti各个数据表的主键可以灵活设置,但是如果使用数字字符串作为其主键,那么按照ID排序,就会带来排序问题,请看代码示例:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, "1", "GroupA", "typeA");createGroup(identityService, "12", "GroupB", "typeB");createGroup(identityService, "13", "GroupC", "typeC");createGroup(identityService, "2", "GroupD", "typeD");createGroup(identityService, "3", "GroupE", "typeE");//根据ID升序排序System.out.println("asc排序结果");List<Group> datas = identityService.createGroupQuery().orderByGroupId().asc().list();for (Group data : datas) {System.out.print(data.getId() + " ");}}
//create方法往上找

代码中,加入了5条用户组数据,需要注意的是,这5条用户组数据,ID分别为:1、12、13、2、3,然后调用orderByGroupId方法,并且设置为升序排序,期望的结果应该是:1、2、3、12、13,而此处输出结果却是:1、12、13、2、3,产生这种现象是由于ID_列字段数据类型是字符型,以MySQL为例,如果字段类型为字符型,而实际存储的是数据的话,那么进行排序时,会将其看作字符型,因此会产生以上的ID顺序错乱。

在MySQL中执行普通的ORDER BY语句,可以看到数据库排序结果与程序结果一致,前面已经讲到,这样的顺序错乱是由于ID_列数据类型为字符型导致,如果需要使用正确的排序,可以使用以下的MySQL语句进行排序:SELECT * FROM ACT_ID_GROUP ORDER BY ID_ ASC,此处在ORDER BY ID_后加了“+0”语句。

如果想在代码中解决该排序问题,可以将Query转换为AbstractQuery,再调用orderBy方法,请见以下代码片断:

AbstractQuery aq = (AbstractQuery)identityService.createGroupQuery();List<Group> datas = aq.orderBy(new GroupQueryProperty("RES.ID_ + 0")).asc().list();

将GroupQuery转换为AbstractQuery,再调用orderBy方法,构造一个GroupQueryProperty,构造参数的字符串为“RES.ID + 0”。执行代码并输出结果后,可以发现结果正确。但笔者不建议使用该方式,因为官方API中并没有提供AbstractQuery与GroupQueryProperty,一旦后面的Activiti版本中修改了这两个类,那我们的代码也需要进行修改。除了该方法,也可以使用Activiti提供的原生SQL查询,详细请见2.2.10章节。

2.2.7、多字段排序

在进行数据查询时,如果想对多个字段进行排序,例如根据名称降序、根据ID升序这样的排序方式,那么在调用asc和desc方法时就需要注意,asc和desc方法会根据Query实例(AbstractQuery)中的orderProperty属性来决定排序的字段,由于orderProperty是AbstractQuery的类属性,因此如果在第二次调用orderByXXX方法后,会覆盖第一次调用时所调置的值。具体测试结果如代码所示:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, "1", "GroupE", "typeB");createGroup(identityService, "2", "GroupD", "typeC");createGroup(identityService, "3", "GroupC", "typeD");createGroup(identityService, "4", "GroupB", "typeE");createGroup(identityService, "5", "GroupA", "typeA");//优先按照id降序、名称升序排序System.out.println("ID降序排序:");List<Group> datas = identityService.createGroupQuery().orderByGroupId().desc().orderByGroupName().asc().list();for (Group data : datas) {System.out.println("    " + data.getId() + "---" + data.getName() + " ");}System.out.println("名称降序排序:");//下面结果将按名称排序datas = identityService.createGroupQuery().orderByGroupId().orderByGroupName().desc().list();for (Group data : datas) {System.out.println("    " + data.getId() + "---" + data.getName() + " ");}}
//create方法往上找

代码的粗体字代码,均使用了两个字段进行排序,第一个查询中,告诉Query实例,使用groupId进行降序排序,再使用名称升序排序,输出结果如下:

ID降序排序:

5---GroupA

4---GroupB

3---GroupC

2---GroupD

1---GroupE

输出结果为优先按照ID进行降序排序,符合预期。第二个查询中,虽然也调用了orderByGroupId方法,但是由于没有马上调用desc方法,而是调用了其他的orderBy方法,因此原来的orderByGroupId方法所设置的排序属性(Query的orderProperty属性)将会被orderByGroupName替换,最终输入结果与之相反。

根据输出结果可以看出,最终按照名称降序排序。根据上面的测试可以看出,asc与desc方法会生成(根据查询条件)相应的查询语句,如果调用了orderByXXX方法却没有调用一次asc或者desc方法,则该排序条件会被下一个设置的查询条件所覆盖。

2.2.8、singleResult方法

该方法根据查询条件,到数据库中查询唯一的数据记录,如果没有找到符合条件的数据,则返回null,如果找到多于一条的记录,则抛出异常,异常信息为:Query return 2 results instead of max 1,代码中使用singleResult方法,并且体现三种查询结果。

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, UUID.randomUUID().toString(), "GroupA", "typeA");createGroup(identityService, UUID.randomUUID().toString(), "GroupB", "typeB");createGroup(identityService, UUID.randomUUID().toString(), "GroupC", "typeC");createGroup(identityService, UUID.randomUUID().toString(), "GroupD", "typeD");createGroup(identityService, UUID.randomUUID().toString(), "GroupE", "typeE");//再写入一条名称为GroupA的数据createGroup(identityService, UUID.randomUUID().toString(), "GroupA", "typeF");//查询名称为GroupB的记录Group groupB = identityService.createGroupQuery().groupName("GroupB").singleResult();System.out.println("查询到一条GroupB数据:" + groupB.getId() + "---" + groupB.getName());//查询名称为GroupF的记录Group groupF = identityService.createGroupQuery().groupName("GroupF").singleResult();//**System.out.println("没有groupF的数据:" + groupF);//查询名称为GroupA的记录,这里将抛出异常Group groupA = identityService.createGroupQuery().groupName("GroupA").singleResult();//**}
//create方法往上

在代码清单中,写入了6条用户组数据,其中需要注意的是最后一条数据,名称与第一条数据名称一致,目的是为了测试使用singleResult方法,在查询到多条记录时抛出异常。代码中的**代码分别为三种情况:正常使用singleResult方法返回第一条数据,查询不到任何数据,查询出多于一条数据抛出异常。程序运行结果如下:

查询到一条GroupB数据:deed85ac-d76c-4e7a-b5f6-4d48eb8340ee---GroupB没有groupF的数据:null16:52:12,936 ERROR CommandContext - Error while closing command contextorg.activiti.engine.ActivitiException: Query return 2 results instead of max 1

2.2.9、用户组数据查询

前面章节中,以用户组数据为基础,讲解了Activiti的数据查询机制以及一些公用的查询方法。Activiti的每种数据均自己对应的查询对象,例如用户组的查询对象为GroupQuery,它继承了AbstractQuery,除了拥有基类的方法(2.2.2至2.2.8的方法)外,它还拥有自己的查询以及排序方法:

groupId(String groupId):根据ID查询与参数值一致的记录。

groupMember(String groupMemberUserId):根据用户ID查询用户所在的用户组,用户组与用户为多对多关系,因此一个用户有可能属于多个用户组。

groupName(String groupName):根据用户组名称查询用户组。

groupNameLike(String groupName):根据用户组名称模糊查询用户组数据。

groupType(String groupType):根据用户组类型查询用户组数据。

orderByGroupId():设置排序条件为根据ID排序。

orderByGroupName():设置排序条件为根据名称排序。

orderByGroupType():设置排序条件为根据类型排序。

potentialStarter(String procDefId):根据流程定义的ID,查询有权限启动该流程定义的用户组。

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据String aId = UUID.randomUUID().toString();createGroup(identityService, aId, "GroupA", "typeA");createGroup(identityService, UUID.randomUUID().toString(), "GroupB", "typeB");createGroup(identityService, UUID.randomUUID().toString(), "GroupC", "typeC");createGroup(identityService, UUID.randomUUID().toString(), "GroupD", "typeD");createGroup(identityService, UUID.randomUUID().toString(), "GroupE", "typeE");// groupId方法Group groupA = identityService.createGroupQuery().groupId(aId).singleResult();System.out.println("groupId method: " + groupA.getId());// groupName方法Group groupB = identityService.createGroupQuery().groupName("GroupB").singleResult();System.out.println("groupName method: " + groupB.getName());// groupType方法Group groupC = identityService.createGroupQuery().groupType("typeC").singleResult();System.out.println("groupType method: " + groupC.getName());// groupNameLike方法List<Group> groups = identityService.createGroupQuery().groupNameLike("%group%").list();System.out.println("groupNameLike method: " + groups.size());}
//create方法往上

注:GroupQuery的设置排序条件方法,在2.2.5至2.2.7小节中已经体现,本小节不再赘述。另外groupMember和potentialStarter方法,将在用户组与用户关系、流程定义章节中描述。

2.2.10、原生SQL查询

各个服务组件中,提供了createNativeXXXQuery的方法,返回NativeXXXQuery的实例,这些对象均是NativeQuery的子接口。使用NativeQuery的方法,可以传入原生的SQL进行数据查询,主要使用sql方法传入SQL语句,使用parameter方法设置查询参数,代码中使用了原生SQL查询用户组数据:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//写入5条用户组数据createGroup(identityService, UUID.randomUUID().toString(), "GroupA","typeA");createGroup(identityService, UUID.randomUUID().toString(), "GroupB","typeB");createGroup(identityService, UUID.randomUUID().toString(), "GroupC","typeC");createGroup(identityService, UUID.randomUUID().toString(), "GroupD","typeD");createGroup(identityService, UUID.randomUUID().toString(), "GroupE","typeE");//使用原生SQL查询全部数据List<Group> groups = identityService.createNativeGroupQuery().sql("select * from ACT_ID_GROUP").list();//**System.out.println("查询全部数据:" + groups.size());//使用原生SQL按条件查询,并设入参数,只查到一条数据groups = identityService.createNativeGroupQuery().sql("select * from ACT_ID_GROUP where NAME_ = 'GroupC'").list();//**System.out.println("按条件查询:" + groups.get(0).getName());//使用parameter方法设置查询参数groups = identityService.createNativeGroupQuery().sql("select * from ACT_ID_GROUP where NAME_ = #{name}").parameter("name", "GroupD").list();//**System.out.println("使用parameter方法按条件查询:" + groups.get(0).getName());}

代码清单星号代码进行了三次原生SQL查询,第二次与第三次查询设置了参数,第三次参数使用了parameter设置参数。由于最终调用查询的是MyBatis的SqlSession,因此写SQL时,需要使用#{}。除了用户组数据外,其他的数据,都可以使用原生SQL查询。运行代码,输出结果如下:

查询全部数据:5按条件查询:GroupC使用parameter方法按条件查询:GroupD

使用原生SQL查询较为灵活,可以满足大部分的业务需求,但笔者还是建议尽量少使用原生SQL查询,这样做增强了代码与数据库结构的耦合性。

2.3、用户管理

作为流程基本元素之一,Activiti同样为用户数据提供了一套管理的 API, 用户数据保存 在 ACT_ID_USER 表中,除了该表外, Activiti还提供了ACT_ID_INFO表用于保存用户信息数据。本节将讲解用户数据的最基本操作,用户信息管理将在 2.4 节中描述。

2.3.1、User对象

与 Group 对象一样,User 对象同样为一个接口,其有一个子接口 UserEntity,实现类为 UserEntitylmpl。UserEntitylmpl 包含以下映射属性。

id:用户 ID,对应 ACT_ID_USER 表的 ID_ 列。

firstName:用户的姓,对应 ACT_ID_USER 表的 FIRST_ 列。

lastName:用户的名,对应 ACT_ID_USER 表的 LAST_ 列。

email:用户邮箱,对应ACT_ID_USER 表的 EMAIL_ 列。

password:用户密码,对应 ACT_ID_USER 表的 PWD_列。

pictureByteArrayld:用户图片的数据记录 ID,对应 ACT_ID_ USER 表的 PICTURE_ID_ 列,该列保存 ACT_GE_BYTEARRA Y 表数据的ID。

revision:该用户数据的版本,对应 ACT_ID_USER 表的 REV_列。

在 API 文档中,只能看到 User 接口的方法, revision 字段并不让外部操作与获取。

2.3.2、添加用户

添加用户与创建用户组类似,Activiti也提供了一个创建 User 实例的方法 (newUser),还提供了一个保存用户数据的方法(saveUser),类似于用户组的 newGroup 与 saveGroup 方法。 使用 newUser 和 saveUser 方法添加用户的示例:

// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到身份服务组件实例
IdentityService identityService = engine.getIdentityService();
String id= UUID.randomUUID().toString();
// 使用 newUser 方法创建 User 实例
User user= identityService.newUser(id);
// 设置用户的各个属性
user.setFirstName("Allen");
user.setLastName("Lee");
user.setEmail("xxx@163.com");
user.setPassword("123");
// 使用 saveUser 方法保存用户
identityService.saveUser(user);
// 根据 id 查询
user = identityService.createUserQuery().userId(id).singleResult(); System.out.println(user.getEmail()) ; 

代码演示了如何使用 newUser 和 saveUser 方法添加用户。用户的保存机制与用 户组相同,在此不再赘述。

2.3.3、修改用户

修改用户同样需要调用 saveUser 方法,调用该方法前需要查询相应的用户数据, 为其设 置需要修改的属性值,最后执行保存操作即可。使用该方法的注意事项,可参考修改用户组的讲述见 2.1.3 节。代码清单 2-14 演示了如何对用户进行修改操作:

public static void main(String [] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService();
String id = UUID.randomUUID().toString();
//创建用户
creatUser(identityService, id, "allen","lee","xxx@163.com", "123");
//查询用户
User user = identityService.createUserQuery().userid(id).singleResult();
user.setEmail("abc@l63.com");
//执行保存
identityService.saveUser(user);
}
//create方法往上

代码只对用户的email字段进行修改,具体效果请查看 ACT_ID_USER表的数据变化,在此不再赘述。

2.3.4、删除用户

用户是流程基本的元素,与用户组同属于 Activiti 的基础数据, 一旦流程或者其他数据引 用了用户的外键,同样会带来无法删除用户的问题,该问题己经在 2.1.4 节中描述过。 删除用户,会先删除本模块中与用户关联的数据,例如图片、用户信息、用户 (用户组) 中间表。 删除用户时, Activiti 选择了删除这些它“知道”的数据。 代码清单 2-15 为一个调用 delete User 方法删除用户的示例。

public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService();
String id = UUID.randomUUID().toString();
//创建用户
creatUser(identityService, id,"allen","lee","xxx@l63.com","123"};
System.out.println("删除前数量:" + identityService.createUserQuery().userId(id).count ()); //删除用户
identityService.deleteUser(id);
System.out.println("删除后数量: " + identityService.createUserQuery().userid(id).count());
}

2.3.5、验证用户密码

IdentityService中提供了一个checkPassword方法来验证用户的密码。在用户模块,需要分清用户与账号的概念。对于 Activiti 来说,用户是一类数据,而账号则是从属于某个用户的数据。此处所说的验证密码,是指验证用户的密码 (ACT_ID_USER表的 PWD_字段),而不是用户账号的密码。关于用户账号,将在 2.4 节中讲解。 checkPassword 方法的第一个参数为 用户数据的 ID(ID 列的值),第二个参数为密码。代码清单 2-16 示范了如何使用 checkPassword 方法验证用户密码。

public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine = ProcessEngines. getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService();
String id = UUID.randomUUID().toString();
//创建用户
creatUser(identityService, id,"allen","lee","xxx@l63.com","abc");
//验证用户密码
System.out.println("验证密码结果:"+ identityService.checkPassword(id,"abc"));
System.out.println("验证密码结果:"+ identityService.checkPassword(id, "c"));
}

2.3.6、用户数据查询

在 2.2 节中讲述了 Activiti 的数据查询机制,与用户组类似,用户同样有一个 UserQuery 查询对象,并且该对象也提供了相应的查询与排序方法,我们也可以调用list或者singleResult方法返回查询数据。UserQuery 中包括了如下这些方法。

userEamil(String email):根据 Email 值查询用户数据。

usreEmailLike(String emailLike):根据 Email 值模糊查询用户数据。

userFirstName(String firstName):根据用户的姓查询用户数据。

userFirstNameLike(S位ing rrstN ameLike):根据用户的姓模糊查询用户数据。

userld(String id):根据用户 D 查询用户数据。

userLastName(String lastName):根据用户的名查询用户数据。

userLastNameLike(String lastNameLike):根据用户的名模糊查询用户数据。

memberOfGroup(String groupld):根据用户组 ID 查询属于该组的全部用户数据。

orderByUserEmail():设置根据 Email 进行排序。

orderByUserFirstName():设置根据用户的姓进行排序。

orderByUserld():设置根据 D 进行排序。

orderByUserLastN ame():设置根据用户的名进行排序。

potentialStarter(String procDefld):根据流程定义的 E 查询有权限启动流程定义的用户 。

UserQuery 的各个查询方法的使用如代码下所示:

public static void main(String[] args) {
//创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService();
String id1 = UUID.randomUUID().toString();
String id2 = UUID.randomUUID().toString();
//创建两个用户
creatUser(identityService, idl,"allen","lee","xxx@l63.com","abc"); creatUser(identityService, id2,"allen2","lee2","xxx2@l63.com","123");
//调用 UserQuery 的各个查询方法
// userid
User user = identityService.createUserQuery().userid(id1).singleResult(); System.out.println("userId: "+ user.getFirstName());
// userFirstName
user = identityService.createUserQuery().userFirstName("allen").singleResult(); System.out.println("userFirstName : " + user.getFirstName() );
//userFirstNameLike
List<User> datas = identityService.createUserQuery().userFirstNameLike("allen%").list(); System.out.println("createUserQuery :" + datas.size());
// userLastName
user= identityService.createUserQuery().userLastName("lee").singleResult();
System.out.println("userLastName:" + user.getFirstName());
// userLastNameLike
datas = identityService.createUserQuery().userLastNameLike("lee%").list();
System.out.println("userLastNameLike:" + datas.size());
// userEmail
user= identityService.createUserQuery().userEmail("xxx@l63.com").singleResult();
System.out.println("userEmail : " + user.getFirstName());
// userEmailLike
datas = identityService.createUserQuery().userEmailLike("%163.com").list();
System.out.println("userEmailLike :" + datas.size());
//使用 NativeQuery
datas = identityService.createNativeUserQuery().sql("select * from ACT_ID_USER where EMAIL_ = #{email}").parameter("email","xxx@163.com").list();
System.out.println("native query:" + datas.get(0).getEmail()) ;
}

在代码中调用了UserQuery提供的大部分查询方法,关于 UserQuery 设置排序 条件的方法在此不再赘述,可查看 2.2.5 节相关内容。memberOfGroup方法会在描述用户组与用户关系的章节中讲述,potentialStarter方法将在流程定义相关章节中详细讲解。

2.3.7、设置认证用户

IdentityService中提供了一个setAuthenticatedUserld方法来将用户ID设置到当前的线程中,setAuthenticatedUserld方法最终调用的是ThreadLocal的set方法。这意味着,如果启动两条线程,在线程中分别调用setAuthenticatedUserld方法,则在相应线程中会输出不同的结果,如代码所示:

public static void main(String[] args){//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//获取身份服务组件实例final IdentityService identityService = engine.getIdentityService();//设置当前线程的userId为1identityService.setAuthenticatedUserId("3");System.out.println("当前线程userId:" + Authentication.getAuthenticatedUserId());//启动两条线程new Thread(){public void run(){try{identityService.setAuthenticatedUserId("1");Thread.sleep(5000);System.out.println("线程1的userId:" + Authentication.getAuthenticatedUserId());} catch (Exception e){}}}.start();new Thread(){public void run(){identityService.setAuthenticatedUserId("2");System.out.println("线程2的userId:" + Authentication.getAuthenticatedUserId());}}.start();
}
//create方法往上

运行代码后结果输出如下:

当前线程userId:3
线程2的userId:2
线程1的userId:1

这里只讲述了如何对用户数据进行管理,这里所说的用户数据,只是指ACT_ID_USER表中的用户数据,关于用户信息等,将在下一节中讲。

2.4、用户信息管理

在 Activiti 中,用户与账号为不同的概念,用户数据已经在上一节讲述过了, Activiti将一 些用户相关的信息抽象出来,使用单独的一个数据表来保存。这些用户相关的信息包括账号信息和自定义的用户信息,这些信息均保存在 ACT_ID_INFO表中。除了这些用户信息外,还有用户的图片资源,图片被保存在 Activiti 公共的资源表 ACT_GE_BYTEARRAY中。本节将讲述如何对这些用户信息进行操作。

2.4.1、添加和删除用户信息

就像用户组与用户的数据一样,同样有一个实体与用户信息映射,对应的实体类为 IdentitylnfoEntityImpl,但是与用户和用户组不一样的是,Activiti 并没有为其提供相应的Query对象,因为这个表的数据结构是 key-value 形式,只需要知道 key 就可以查询到相应的数据。以下为添加和删除用户方法的描述:

setUserlnfo(String userld, String key, S位ing value):为用户添加用户信息,第一个参数为 用户数据的ID,第二个参数为用户信息的名称,第三个参数为该用户信息的值。

deleteUserlnfo(String userld, String key):删除用户信息,第一个参数为用户ID, 第二 个参数为用户信息的名称。

IdentityService的添加与删除用户信息方法展示:

public static void main(String[] args) {
// 创建流程引擎
ProcessEngine engine= ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService();
String id = UUID.randomUUID().toString();
//创建用户
creatUser(identityService,id,"allen","lee","xxx@163.com","abc");
// 创建一个用户信息
identityService.setUserinfo(id,"age","26");
//创建第二个用户信息
identityService.setUserinfo(id,"weight","60KG");
//删除用户年龄信息
identityService.deleteUserinfo(id,"age");
}
//create方法往上找

2.4.2、查询用户信息

成功将用户信息保存到数据库后,如果需要使用这些信息,则可以调用 IdentityService 的getUserinfo方法来查找这些信息,使用该方法需要提供用户的ID与信息的键值。调用该方法 后,将返回相应信息的字符串 。getUserinfo 方法示例:

// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine ();
//得到身份服务组件实例
IdentityService identityService = engine.getidentityService ();
String id= UUID.randomUUID().toString();
// 创建用户
creatUser(identityService, id, "anllen","lee" , "xxx@l63.com", "abc");
//创建一个用户信息
identityService.setUserinfo(id, "age" , "30");
// 创建第二个用户信息
identityService.setUserinfo(id, "weight","60KG");
//查询用户信息
String value =identityService.getUserinfo(id,"age");
System.out.println("用户年龄为:"+ value);

调用 getUserlnfo 方法可以查询全部的用户信息,只需要知道用户ID和信息键值即可。代码输出结果如下:

用户年龄为:30

2.4.3、设置用户图片

Activiti 专门提供了一个数据表来保存文件数据,即 ACT_GE_BYTEARRAY 表。 Activiti 会读取文件,并将这些文件转换为 byte 数组,然后保存到表中 。IdentityService 提供了 setPicture 和getUserPicture 方法用来设置和查找用户图片数据,其中对 setUserPicture 方法 需要设置两个参数:用户ID和 Picture 实例。 setUserPicture 方法设置用户图片的示例:

public static void main(String[] args) throws Exception{//创建流程引擎ProcessEngine engine = ProcessEngine.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();String id = UUID.randomUUID().toString();//创建用户creatUser(identityService,id,"allen","lee","xxx@163.com","123");//读取图片转换为byte数组FileInputStream fis = new FileInputStream("resource/artifact/picture.png");BufferedImage img = ImageIO.read(fis);ByteArrayOutputStream output = new ByteArrayOutputStream();ImageIO.write(img,"png",output);//获取图片的byte数组byte[] picArray = output.toByteArray();//创建picture实例Picture picture = new Picture(picArray,"image");//为用户设置图片identityService.setUserPicture(id,picture);}
//creat方法往上。

在代码中, 先创建输入流实例,读取一份 png 文件,然后将该图片文件的内容 转换为 byte 数组,根据这个 byte 数组,创建一个 Picture实例,最后调用 setUserPicture 方法 为用户设置相应的图片。

在 setUserPicture 方法中,会判断该用户是否存在图片(ACT_ID_USER表的PICTURE_ID字段),如果存在,则将原来的图片数据(ACT_GE_BYTEARRAY表的数据)删除,然后再将新的图片数据写入ACT_GE_BYTEARRAY 表中 ,然后再为该用户绑定图片数据 的ID (ACT_ID_USER表的PICTURE_ID字段)。运行代码清,可看到 ACT_GE_BYTEARRAY 表和 ACT_ID_USER 表的数据更改了。用户数据表中的 PICTURE_ID字段保存了 ACT_GE_BYTEARRAY 表的ID。

注意,如果数据为用户图片,则对应的映射实体为 ByteArrayEntity;如果数 据为部署资源,则为 ResourceEntity。

2.5、用户组与用户的关系

在 Activiti中,一个用户可以被分配到多个用户组中 ,一个用户组中可以包含多个用户,针对这种情况,Activiti 使用了中间表来保存这两种数据间的关系。通过关系表的数据,可以清楚地看到用户组与用户之间的关系,Activiti 也提供了相应的 API 来操作这种关系。这个中间表为 ACT_ID_MEMBERSHIP表,这里需要注意的是,Activiti 井没有为这个表做实体的映射。

2.5.1、绑定关系

绑定关系,意味着向 ACT_ID_MEMBERSHIP表写入一条关系数据,只需要指定这个关系的用户ID和用户组 ID,再调用 IdentityService 的 createMembership 方法即可,该方法第一 个参数为用户ID,第二个参数为用户组 ID。 该方法绑定用户组与用 户关系的示例:

public static void main(String[] args){//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();//保存一个用户User user = creatUser(identityService,UUID.randomUUID().toString(),"allen","lee","xxx@163.com","123");//保存一个用户组Group group = createGroup(identityService,UUID.randoUUID.toString,"经理组","manager");//绑定关系identityService.createMembership(user.getId(),group.getId());
}
//create用户往上。
//create组往上。

在代码中,使用 createUser 和 createGroup 方法创建了一个用户与一个名为 “经理组”的用户组,这两个方法保存相应数据后,均会返回相应的实例,最后调用代码清单中的绑定关系代码绑定它们之间的关系。

2.5.2、解除绑定

解除用户组与用户的关系绑定,无非是将 ACT_ID_MEMBERSHIP 表中的关系数据删除, IdentityService 提供了一个 deleteMembership 方法用于将 ACT_ID _MEMBERSHIP表中的数据删除。使用该方法需要提供用户ID与用户组ID, 该方法的使用如以下代码所示:

identityService.deleteMembership(user.getid(),group.getid()) ; 

在此需要说明的是,如果用户ID和用户组ID对应的数据并不存在于数据库中,执行 deleteMembership 方法也不会抛出任何异常,也不会对数据库中的数据产生任何的影响。

2.5.3、查询用户组下的用户

在2.3.6节中讲解了关于用户的各个查询方法,但并没有讲述memberOfGroup方法的使用, 下面将讲述该方法的使用。根据中间表的设计,大致可以猜到memberOfGroup 方法的实现过程,先到 ACT_ID _MEMBERSHIP表中根据groupId进行查询,获取全部的userid后,再到 ACT_ID_USER表中查询用户数据。使用该方法查询用户组下用户的示例:

   public static void main(String[] args) {
//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例IdentityService identityService = engine.getidentityService();
//保存两个用户User userl = creatUser(identityService,UUID.randomUUID().toString(), "张经理","young","yangenxiong@l63.com","abc");User user2 = creatUser(identityService,UUID.randomUUID().toString(), "李经理","young2","yangenxiong@163.com","abc";
//保存一个用户组Group group= createGroup(identityService,UUID.randomUUID().toString(),"经理组","manager"); //将两个用户分配到用户组下identityService.createMembership(userl.getid(),group.getid());identityService.createMembership(user2.getid(),group.getid());List<User> users = identityService.createUserQuery().memberOfGroup(grop.getid()).list() ; System.out.println("经理组有如下人员:");for(User user : users) {System.out.println(user.getFirstName());}}
//create方法往上

在代码中,创建了两个用户(张经理与李经理)和一个用户组 (经理组), 并绑 定两个用户与该用户组的关系,然后使用代码中的List代码查询经理组下面的全部用户。

2.5.4、查询用户所属的用户组

与memberOfGroup 方法类似,groupMember方法会根据用户的ID,查询该用户所属的用户组。同样,该方法会到中间表中查询该用户的全部关系数据并且得到 groupId,然后根据这 些 groupId 再到用户组的表中查询用户组数据。该方法的使用方法如代码 所示:

public static void main(String[] args) {
//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
//得到身份服务组件实例IdentityService identityService = engine.getIdentityService();
//保存一个用户User userl = creatUser(identityService,UUID.randomUUID().toString(), "张经理","young","yangenxiong@l63.com","abc");//User user2 = creatUser(identityService,UUID.randomUUID().toString(), "李经理","young2","yangenxiong@163.com","abc";
//保存两个用户组Group group1= createGroup(identityService,UUID.randomUUID().toString(),"经理组","manager");Group group2= createGroup(identityService,UUID.randomUUID().toString(),"员工组","manager");
//将两个用户分配到用户组下identityService.createMembership(userl.getid(),group1.getid());identityService.createMembership(user1.getid(),group2.getid());//调用memberOfGroup方法查询用户所在组//List<User> users = identityService.createUserQuery().memberOfGroup(grop.getid()).list() ;List<Group> groups = identityService.createGroupQuery().groupMember(grop.getid()).list();System.out.println("张三属于的组有:");for(User group : groupss) {System.out.println(group.getName());}}

2.6、小结

本章主要讲解了身份服务组件 IdentityService 的使用, IdentityService 的 API 包括用户组 数据的管理、用户数据管理、用户信息管理和关系数据管理。除此之外,特别讲解了 Activiti 的数据查询设计(查询对象)以及基础查询方法的使用, Activiti 的各类数据均会使用这种方式进行查询。在对这些身份数据进行操作的过程中,特别讲解了使用这些 API 对数据所产生 的影响,以便读者在此过程中更深入地体会Activiti 的数据库设计方法。在学习本章内容时,最重要的是掌握 Activiti 的各个查询方法,关于业务组件各个方法的 使用及注意点,可以在以后有需要时再翻看本章。

3、流程存储

在第1 章中,我们对各个服务组件有了初步的认识,在第 2 章中了解了身份服务组件 IdentityService 的使用,本章将介绍 ProcessEngine 的另一个服务组件:流程存储服务组件 RepositoryService。 RepositoryService 主要用于对 Activiti 中的流程存储的相关数据进行操作, 这些操作包括对流程存储数据的管理、流程部署以及对流程的基本操作等。

3.1、流程文件部署

RepositoryService 负责对流程文件的部署以及流程的定义进行管理,不管是JBPM还是Activiti等工作流引擎,都会产生流程文件,工作流引擎需要对这些文件进行管理,这些文件包括流程描述文件、流程图等。

在 Activiti 中,如果需要对这些资源文件进行操作(包括添加、 删除、 查询等),可以使 用 RepositoryService 提供的 API。 这些文件数据被保存在 ACT_GE_BYTEARRAY 表中,对应的实体为ResourceEntitylmpl。在此需要注意的是,在 2.4.3 节中讲述了用户图片,设置用户图片同样会向 ACT_GE BYTEARRAY表中写入数据,但是当数据为用户图片时,对应的实体是 ByteArrayEntitylmpl。ResourceEntitylmpl 与 ByteArrayEntitylmpl区别如下。

ByteArrayEntitylmpl 对应的数据有版本管理数据,而 ResourceEntitylmpl 则没有。

ResourceEntitylmpl 会设置 ACT_GE_ BYTEARRA Y 表的 GENERATED_字段值,而 ByteArrayEntitylmpl 在进行保存时,该值为 null。

3.1.1、Deployment对象

Deployment 对象是一个接口,一个 Deployment 实例表示一条ACT_RE_DEPLOYMENT表的数据,同样,Deployment也遵循 Activiti的实体命名规则,子接口为DeploymentEntity,实现类为 DeploymentEntitylmpl。如果要对属性进行修改,需要调用DeploymentBuilder提供的方法,Deployment只提供了一系列getter方法。DeploymentEntitylmpl中包含以下映射属性。

id:主键,对应 ACT_RE_DEPLOYMENT 表的ID_列。

name:部署名称,对应 ACT_RE_DEPLOYMENT 表的 NAME_ 列。

deploymentTime:部署时间,对应 DEPLOY_TIME_ 列。

category:部署的类别,对应 CATEGORY_列。

tenantld:在云时代,同一个软件有可能被多个租户所使用,因此 Activiti 在部署、流程定义等数据中都预留了tenantld 字段。

key: 为部署设置键属性,保存在KEY_列。

3.1.2、DeploymentBuilder对象

对流程文件进行部署,需要使用DepoymentBuilder对象,获取该对象,可以调用RepositoryService的createDeployment方法,具体如以下代码所示:

//得到流程存储服务实例
RepositoryService repositoryService = engine.getRepositoryService();
//创建DepoymentBuilder实例
DepoymentBuilder builder = repositoryService.createDepoyment();

DepoymentBuilder中包含了多个addXXX方法,可以用于为部署添加资源,这些方法有:

addClasspathResource(String resource):添加classpath下的资源文件。

addInputStream(String resourceName,InputStream):添加输入流资源。

addString(String resourceName,String text):添加字符串资源。

addZipinputStream(ZipinputStream inputStream):添加 zip 压缩包资源。

addBpmnModel(String resourceName,BpmnModel bpmnModel):解析 BPMN 模型对象, 并作为资源保存。

addBytes(String resourceName, byte[] bytes): 添加字节资源。

除此之外,还提供了修改部署信息的方法, 例如 key、 name、 category 等。 下面将讲述这 些方法的使用。

3.1.3、添加输入流资源

在DeploymentEntitylmpl类中,使用一个Map来维护资源,表示一次部署中会有多个资源,就是我们平常所说的一对多关系。调用DeploymentBuilder的addinputStream方法,实际上就是往DeploymentEntitylmpl的Map里面添加元素,Map的key是资源名称,value是解析InputStream后获得的byte数组。代使用 addinputStrean方法添加部署流程图资源示例:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//第一个资源输入流InputStream is1 = new FileInputStream(new File("resource/intput.png"));//第二个资源输入流InputStream is2 = new FileInputStream(new File("resource/intput.png"));//创建DeploymentBuilder实例DeploymentBuilder builder = repositoryService.createDeployment();//为DeploymentBuilder添加资源输入流builder.addInputStream("inputA",is1);builder.addInputStream("inputB",is2);//执行部署方法builder.deploy();}

在代码中,调用了两次addinputStream方法添加了两个资源,这两个资源分别是两份png文件(流程图片),最后执行 deploy 方法。

3.1.4、添加classpath资源

与addlnputStream方法类似,addClasspathResource方法也是往部署实体的Map里面添加元素,但不同的是, addClasspathResource方法会得到当前的ClassLoader对象 。调用 getResourceAsStream方法将指定的classpath下的资源文件转换为InputStream,再调用addlnputStream 方法。代码示范了如何使用addClasspathResource 方法将流程图资源保存到数据库中。

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//创建DeploymentBuilder实例DeploymentBuilder builder = repositoryService.createDeployment();//添加class path下的资源builder.addClasspathResource("resource/classpath.png");//执行部署方法builder.deploy();}

使用 addClasspathResource方法将一份图片文件保存到ACT_GE_ BYTEARRAY表中。需要注意的是,使用 addClasspathResource方法并不需要指定名称参数,Activiti会使用 其传入的路径作为资源的名称。

3.1.5、添加字符串资源

如果需要将一些流程定义的属性或者公用的变量保存起来,则可以使用 addString 方法。前面讲述了addinputStream和 addClasspathResource方法,关于 addString 方法,也可以猜到它的实现逻辑,实际上就是调用String的getBytes方法获得字节数组,再将其放到部署对象的Map中。调用 addString方法与调用addlnputStream方法类似,在此不再赘述。

3.1.6、添加压缩包资源

在实际应用中,可能需要将多个资源部署到流程引擎中,如果这些关联的资源被放在一个压缩包(zip 包)中,则可以使用 DeploymentBuilder提供的addZiplnputStream方法直接部署压缩包,该方法会遍历压缩包内的全部文件,然后将这些文件转换为 byte 数组,写到资源表中。addZiplnputStream 方法添加资源示例:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//创建DeploymentBuilder实例DeploymentBuilder builder = repositoryService.createDeployment();//获取zip文件的输入流FileInputStream fis = new FileInputStream(new File("resource/ZipInputStream.zip"));//读取zip文件,创建zipinputstream(fis)ZipInputStream zi = new ZipInputStream(fis);//添加zip压缩包资源builder.addZipInputStream(zi);//执行部署方法(写入数据库)builder.deploy();}

代码先取得 zip 文件的输入流,然后创建一个 ZiplnputStream对象,最后调用addZiplnputStream方法。在本例中,测试的zip包包含两份文件:InputStreaml.txt 和 InputStrearn2.txt,最终 addZiplnputStream 方法产生两条数据为测试压缩包内的两份文件, 由此可见,addZipinputStream方法读取压缩包内的全部文件并将它们转换为byte数组,然后写到 ACT_GE_BYTEARRAY 表中。

3.1.7、添加BPMN模型资源

DeploymentBuilder提供了一个add.BpmnModel方法,可传入 BPMN规范的模型(BpmnModel类)来进行部署,add.BprnnModel 方法示例:

  public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//创建DeploymentBuilder实例DeploymentBuilder builder = repositoryService.createDeployment();builder.addBpmnModel("MyCodeProcess",createProcessModel()).name("MyCodeDeploy").deploy();}//create方法private static BpmnModel createProcessModel() {//创建BPMN模型对象BpmnModel model = new BpmnModel();org.activiti.bpmn.model.Process process = new org.activiti.bpmn.model.Process();model.addProcess(process);process.setId("myProcess");process.setName("My Process");//开始事件StartEvent startEvent = new StartEvent();startEvent.setId("startEvent");process.addFlowElement(startEvent);//用户任务UserTask userTask = new UserTask();userTask.setName("User Task");userTask.setId("userTask");process.addFlowElement(userTask);//结束事件EndEvent endEvent = new EndEvent();endEvent.setId("endEvent");process.addFlowElement(endEvent);//添加流程顺序process.addFlowElement(new SequenceFlow("startEvent","userTask"));process.addFlowElement(new SequenceFlow("userTask","endEvent"));return model;}

代码中的createProcessModel方法,通过编码的方式创建了 BPMN 的流程模型, 最终在返回的BpmnModel中,包括一个开始事件、一个用户任务、一个结束事件。运行代码该流程模型会被保存到数据库中,在保存的过程中,会产生流程的 XML 文件并被 写到资源表中。产生的 XML 如下所示:

<process id="myProcess" name="My Process" isExecutable="true"><startEvent id="startEvent"></startEvent><userTask id="userTask” name="User Task"></userTask><endEvent id="endEvent"></endEvent><sequenceFlow sourceRef="startEvent" targetRef=" userTask"></sequenceFlow><sequenceFlow sourceRef="userTask" targetRef="endEvent"></sequenceFlow></process> 

除了前面所讲述的几个addXXX方法外,还有一个addBytes方法,该方法较为简单,直接将资源名称与byte数组添加到 DeploymentEntitylmpl的Map中,在此不再赘述。

3.1.8、修改部署信息

使用DeploymentBuilder的name、key、category、tenanld方法可以设置部署属性,保存后会将数据保存到 ACT_ DEPLOYMENT表的对应字段中。这几个方法的使用如代码所示:

 public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//创建DeploymentBuilder实例DeploymentBuilder builder = repositoryService.createDeployment();//设置各个属性builder.name("crazyit").tenantId("tenanId").key("myKey").category("myCategory");//执行部署(写入数据库中)builder.deploy();}

3.1.8、过滤部署重复

进行了第一次部署后,资源没有发生变化而再次进行部署,同样会将部署数据写入数据库中,想避免这种情况,可以调用 DeploymentBuilder的enableDuplicateFiltering 方法,该方法仅将 DeploymentBuilder的isDuplicateFilterEnabled属性设置为 true。在执行deploy方法时,如果发现该值为true,则根据部署对象的名称去查找最后一条部署记录,如果发现最后一条部署记 录与当前需要部署的记录一致,则不会重复部署。这里所说的记录一致,是指 DeploymentEntity下的资源是否相同,包括资源名称与资源内容。详情请见代码清单:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//创建DeploymentBuilder实例DeploymentBuilder builderA = repositoryService.createDeployment();builderA.addClasspathResource("resource/DuplicateFilter.txt");builderA.name("DuplicateFilterA");builderA.deploy();//由于资源一样,并且调用了enableduplicatefiltering方法,因此不会再写入数据库中DeploymentBuilder builderB = repositoryService.createDeployment();builderB.addClasspathResource("resource/DuplicateFilter.txt");builderB.name("DuplicateFilterA");builderB.enableDuplicateFiltering();builderB.deploy();//由于资源发生变化,即使调用了enableduplicatefiltering方法,也会写到数据库中DeploymentBuilder builderC = repositoryService.createDeployment();builderC.addClasspathResource("resource/DuplicateFilter.txt");builderC.name("DuplicateFilterA");builderC.enableDuplicateFiltering();builderC.deploy();}

在代码清单中,创建了三个 DeploymentBuilder 对象, 进行了三次部署,第二次部署不会将数据写入数据库,因为部署的资源与第一次的一样,且调用了enableDuplicateFiltering 方法。

7.1.10、取消部署时的验证

默认情况下,在部署时会对流程的XML文件进行验证,包括验证是否符合BPMN2.0的规范、定义的流程是否可执行。如果 XML 文件不符合规范或者定义的流程不可执行,那么将 会在部署时抛出异常。如果想跳过这两个验证,可以调用DeploymentBuilder 的disableSchemaValidation与disableBpmnValidation方法。先定义两个有问题的流程文件,以便验证,如两个代码清单所示:

<process id="vacationProcess" name="vacation"><userTask id="usertaskl" name="User Task” ></userTask><endEvent id="endeventl" name="End"></endEvent><sequenceFlow id="flow2" name="" sourceRef ="usertaskl" targetRef="endeventl"> </sequenceFlow><startEvent id="starteventl" name="Start"></startEvent><sequenceFlow id="flow3" name="" sourceRef=" starteventl" targetRef="usertaskl">
</sequenceFlow>
</process>
<abc></abc>
<process id="vacationProcess" name="vacation" isExecutable="true"><startEvent id="startenent1" name="Start"></startEvent><userTask id="usertaskl" name="User Task” ></userTask><startEvent id="startevent2" name="Start"></startEvent><sequenceFlow id="flow1" sourceRef ="starteventl" targetRef="usertaskl"> </sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertaskl" targetRef="startevent2">
</sequenceFlow>
</process>

在第一个代码清单中,在process节点下面加入了一个 abc 节点,由于 BPMN规范中并没有abc节点,因此部署该流程文件会抛出异常。第二个代码清单中的XML文件虽然格式没有问题,但是流程存在问题,开始事件连接到用户任务,用户任务再连接到另一个开始事件,该流程存在明显的问题,因此部署该文件时同样会抛出异常。在下面代码清单中部署了这两份文件,但不会抛出异常。

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//部署一份错误的xml文件不会报错DeploymentBuilder builderA = repositoryService.createDeployment();builderA.addClasspathResource("bpmn/xmlError.bpmn").disableSchemaValidation().deploy();//部署一份不可执行的bpmn文件,不会报错DeploymentBuilder builderB = repositoryService.createDeployment();builderB.addClasspathResource("bpmn/bpmnError.bpmn").disableBpmnValidation().deploy();}

在代码清单中创建了两个 DeploymentBuilder,分别部署了两个有问题的流程文件,且分别调用了两个取消验证的方法,但运行代码并不会抛出异常,最终流程正常部署。注意:不建议取消验证,部署时进行验证可以提前发现问题。

3.2、流程定义的管理

这里所说的流程定义管理是指由RepositoryService提供的一系列对流程定义的控制,包括中止流程定义、激活流程定义和设置流程权限等。

3.2.1、ProcessDefinition对象

ProcessDefinition对象是一个接口,一个 ProcessDefinition实例表示一条流程定义数据,在Activiti 中,它的实现类为 ProcessDefinitionEntitylmpl,对应的数据表为ACT_RE_PROCDEF。ProcessDefinition接口有以下方法。

getCategory:返回流程定义的category属性,对应CATEGORY_字段的值。

getDeploymentld:返回部署的id,这个流程定义是由哪次部署产生的,对应字段为DEPLOYMENT_ID_。

getDescription:返回流程定义的描述。

getDiagramResourceName:如果流程定义有流程图的话,将返回流程图对应的资源名称。

getEngineVersion:返回流程引擎版本,当前无须关心该方法。

getld:返回流程定义主键。

getKey:返回流程定义的名称,此名称唯一。

getName:返回流程定义显示的名称。

getResourceName:在部署时,会将流程定义的XML文件存到资源表中,该方法返回资源的名称。

getTenantld:返回租户ID。

getVersion:返回流程定义的版本号,对应 VERSION_字段,注意并不是revision属性。

hasGraphicalNotation:该流程定义文件是否有流程图的XML元素。

hasStartFormKey:流程的开始事件中是否存在activiti:formKey 的定义。

isSuspended:是否为中断状态,SUSPENSION_STATE_字段值为1表示激活状态,值为2则表示中断状态。

3.2.2、流程部署

流程部署实际上就是将流程的描述文件写入数据库中,Activiti在获取资源文件后,会对其后缀进行解析,如果后缀名为“BPMN 20.xrnl”或者“bpmn”,则它们均会被看作流程描述文件,交由Activiti的Deployer(部署流程描述文件实现类为BpmnDeployer)进行部署。部署流程描述文件示例:

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//部创建DeploymentBuilder实例DeploymentBuilder builder = repositoryService.createDeployment();builder.addClasspathResource("bpmn/xmlError.bpmn").deploy();}

上面代码清单中最后一行代码,部署 classpath 下的 bpmn/processDeploy.bpmn 文件,该文 件是一个规范的流程描述文件,因此部署成功。processDeploy.bpmn 文件内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<definitionns xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn"xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC"xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"
typeLanguage="http://www.w3.org/2001/XMLSchema"expressionLanguage="http://www.w3.org/1999/XPath"
targetNamespace="http://www.activiti.org/test"> <process id="vacationProcess" name="vacation"> <userTask id="usertaskl" name="User Task"></userTask> <endEvent id="endeventl" name="End"></endEvent><sequenceFlow id="flow2" name="" sourceRef="usertaskl" targetRef="endeventl"> </sequenceFlow> <startEvent id="starteventl" name="Start"></startEvent> <sequenceFlow id="flow3" name="" sourceRef="starteventl" targetRef="usertaskl"> </sequenceFlow> </process> <bpmndi:BPMNDiagram id="BPMNDiagram_vacationProcess"> <bpmndi:BPMNPlane bpmnElement="vacationProcess "id="BPMNPlane_vacationProcess"> <bpmndi:BPMNShape bpmnElement="usertaskl" id="BPMNShape_usertaskl"> <omgdc:Bounds height="55" Width="105" x="310" y="160"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="endeventl" id="BPMNShape_endeventl"><omgdc:Bounds height="35" width="35" x="490" y="170"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNShape bpmnElement="starteventl" id="BPMNShape_starteventl"> <omgdc:Bounds height="35" width="35" x="170" y="170"></omgdc:Bounds> </bpmndi:BPMNShape> <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2"> <omgdi:waypoint x="415" y="187"></omgdi:waypoint> <omgdi:waypoint x="49o” y="187"></omgdi:waypoint> </bpmndi:BPMNEdge> <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3"> <omgdi:waypoint x="205" y="187"></omgdi:waypoint> <omgdi:waypoint x="310" y="187"></omgdi:waypoint> </bpmndi:BPMNEdge> </bpmndi:BPMNPlane> </bpmndi:BPMNDiagram>
</definitions> 

需要注意的是,bpmndi:BPMNDiagram节点定义的是流程图的XML元素,process定义的是流程的元素,本书为了节省篇幅, 后面章节不会贴出流程图的 XML 元素的代码清单。上面代码清单定义了一个最简单的流程,有一个开始事件,一个 UserTask 和结束事件。
流程描述文件的具体内容,将在讲述BPMN2.0 规范的章节中详细介绍。将流程文件部署后,除前面章节所讲的部署表和资源表中会加入数据外,流程定义表(ACT_RE_PROCDEF) 中也会产生流程定义数据。

ACT_RE_PROCDEF数据表 ID_列的生成规则为,由流程 XML文件的process 节点的id作为前缀,加上该流程定义的部署版本号(VERSION_)字段,再加上生成的主键。例如 process 节点的id为vacation,第二次进行部署,数据库自增长 id 为 10001 ,最终id为vacation:2:10001。

3.2.3、流程图部署

在3.1 节中,讲述了DeploymentBuilder的各个方法的使用,其中addClasspathResource方法、addZiplnputStream方法和 addlnputStream方法均可以将文件数据写到数据库中,因此,可以使用这些方法将流程图放到数据库中,这些方法的使用,参看本章 3.1节的讲述。代码清单3-12示范了如何使用addClasspathResource方法将流程图部署到数据库中。

public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//部署流程描述文件与流程图DeploymentBuilder dep = repositoryService.createDeployment().addClasspathResource("bpmn/diagram.bpmn").addClasspathResource("bpmn/diagram.png").disableBpmnValidation();//**//查询流程定义实体ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();//输出结果为bpmn/diagram.vacationProcess.pngSystem.out.println(def.getDiagramResourceName());}

代码清单中的带**代码,使用addClasspathResource方法将流程描述文件和流程图部署到数据库中,最终会在资源表(ACT_GE_ BYTEARRAY)中写入两条记录:一条为流程描述文件数据,另外一条为流程图片的文件数据。另外,流程定义表(ACT_RE_ PROCDEF) 的DGRM_RESOURCE_NAME_宇段的值为飞pmn/diagram.png”。

3.2.4、流程图自动生成

如果在部署时我们不提供流程图,但在流程定义的 XML 文件中保存了 BPMN 流程图的 元素,则 Activiti 会自动生成流程图,并保存到资源表中。如果不希望 Activiti 帮我们生成流程图,则可以在流程引擎配置文件中加入以下属性:

<property name="createDiagramOnDeploy" value="false"/>

使用方法如下:

 public static void main(String[] args) {//创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//得到流程存储服务组件实例RepositoryService repositoryService = engine.getRepositoryService();//部署流程描述文件与流程图Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/diagram.bpmn").deploy();//查询流程定义实体ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();//输出结果为bpmn/diagram.vacationProcess.pngSystem.out.println("自动生成流程图" + def.getDiagramResourceName());//读取不生成流程图的配置文件ProcessEngineConfiguration config = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("noGenDiagram.cfg.xml");ProcessEngine noGenEngine = config.buildProcessEngine();RepositoryService noGenService = noGenEngine.getRepositoryService();Deployment noGenDep = noGenService.createDeployment().addClasspathResource("bpmn/noGenDiagram.bpmn").deploy();ProcessDefinition noGenDef = noGenService.createProcessDefinitionQuery().deploymentId(noGenDep.getId()).singleResult();//输出结果为nullSystem.out.println("不生成流程图,查询资源为:" + noGenDef.getDiagramResourceName());}

3.2.5、中止与激活流程定义

RepositoryService中提供了多个中止与激活流程定义的方法,可以将流程定义的数据置为中止与激活状态。其中有多个 suspendProcessDefinitionByld、suspendProcessDefinitionByKey重载的中止方法,也有多个activateProcessDefinitionByld、 activateProcessDefinitionByKey重载的激活方法。以activateProcessDefinitionByld方法为例,有两个重载方法。

activateProcessDefinitionByld(String processDefinitionld):根据流程定义的 id 激活流程 定义。
activateProcessDefinitionByld(String processDefinitionld, boolean activateProcesslnstances, Date activationDate):在某个时间激活流程定义,需要注意的是activateProcesslnstances参数,如果为true,则该流程定义下的流程实例,也会被激活。

本身流程定义就没有所谓的中止与激活的概念(流程才有),这里所说的中止与激活,只是流程定义的数据状态设置为中止状态和激活状态。流程定义文件一旦被部署,那么对应的流程定义数据状态为激活,可以调用 Reposit。可Service 的中止流程定义的方法改变其状态。RepositoryService 提供了两个中止流程定义的方法。

suspendProcessDefinitionByld(String processDefinitionld):根据流程ID中止流程定义。

suspendProcessDefinitionByKey(String processDefinitionKey): 根据流程定义文件中的 process 节点的 id 属性中止流程定义,也可以看作根据 ACT_RE_PROCDEF 表中的KEY_字段值中止流程定义。

process 节点的 id 属性中止流程定义,也可以看作根据 ACT_RE_PROCDEF 表中的KEY_字段值中止流程定义。当流程定义被中止后,如果想激活流程定义,同样可以使用 RepositoryService中提供的激活流程定义的方法,方法描述如下。

activateProcessDefinitionByld(String processDefinitionld):根据流程ID激活流程定义。

activateProcessDefinitionByKey(String processDefinitionKey):根据流程的 key 激活流程定义,与中止流程定义的 suspendProcessDefinitionByKey方法一致。使用中止流程与激活流程的方法的示例:

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/suspendProcessDef.bpmn").deploy();//查询流程定义实体ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();       // 调用suspendProcessDefinitionById中止流程定义 repositoryService.suspendProcessDefinitionById(def.getId());// 调用activateProcessDefinitionById激活流程定义 repositoryService.activateProcessDefinitionById(def.getId());// 调用suspendProcessDefinitionByKey中止流程定义 repositoryService.suspendProcessDefinitionByKey(def.getKey());// 调用activateProcessDefinitionByKey激活流程定义 repositoryService.activateProcessDefinitionByKey(def.getKey());}

代码中的后四行部分,调用了中止流程定义与激活流程定义的四个方法, 需要注意的是,如果一 个流程定义状态已经为中止状态,再调用中止方法,将会抛出 ActivitiException,激活亦然。流程定义一旦被置为中止状态,那么该流程将不允许被启动。 其他重载方法的使用与代码清单类似,在此不再赘述。

3.2.6、流程定义缓存配置

为了减少数据的查询,提升流程引擎性能,Activiti本身对某些常用的数据做了缓存。例如在解析完流程定义的XML文件后,会将流程定义缓存到一个Map 中,key为流程定义的id (数据库的ID_字段),value 为封装好的缓存对象。默认情况下,不需要进行配置,流程引擎 会进行缓存的工作。以下代码会输出缓存内容。

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();ProcessEngineConfigurationImpl config = (ProcessEngineConfigurationImpl) engine.getProcessEngineConfiguration();// 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 进行10次部署for (int i = 0; i < 10; i++) {repositoryService.createDeployment().addClasspathResource("bpmn/default-cache.bpmn").name("dep_" + i).key("key_" + i).deploy();}// 获取缓存DefaultDeploymentCache cache = (DefaultDeploymentCache) config.getProcessDefinitionCache();// 遍历缓存,输出Map中的keyfor (Iterator it = cache.cache.keySet().iterator(); it.hasNext();) {String key = (String) it.next();System.out.println(key);}}

在代码清单中,先进行10次部署,然后读取缓存对象,获取Map属性并输出内容,需要注意以下细节:

为了测试获取缓存的Map,代码清单的包与DefaultDeploymentCache的包是一致的,换言之,Map是protected的。

DefaultDeploymentCache、ProcessEngineConfigurationImpl等实现类不应出现在业务代码中,尽量使用它们的接口。

运行代码可知,由于缓存没有限制,因此部署了10次,缓存(Map)中就有10个元素,如果想限制缓存数量,可以在流程引擎的配置文件中使用以下配置:

<property name="processDefinitionCacheLimit" value="2" />

以上配置表示缓存中只保存两个流程定义的缓存对象, 一般情况下,只缓存最后两次部署的流程定义。下面代码清单为测试该配置的代码。

public static void main(String[] args) {// 创建流程引擎ProcessEngineConfigurationImpl config =(ProcessEngineConfigurationImpl) ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("use-limit.cfg.xml");ProcessEngine engine = config.buildProcessEngine();// 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 进行10次部署for (int i = 0; i < 10; i++) {repositoryService.createDeployment().addClasspathResource("bpmn/default-cache.bpmn").name("dep_" + i).key("key_" + i).deploy();}// 获取缓存DefaultDeploymentCache cache = (DefaultDeploymentCache) config.getProcessDefinitionCache();// 遍历缓存,输出Map中的keyfor (Iterator it = cache.cache.keySet().iterator(); it.hasNext();) {String key = (String) it.next();System.out.println(key);}}

代码中的前3行,读取了processDefinitionCacheLimit 为2的配置文件,以该配置文件来创建流程引擎,进行10次部署,最后读取缓存并输出。运行代码, 仅仅输出最后两次部署的流程定义数据的 id。在实际中,可以根据自身产品的业务来决定缓存数量。

3.2.7、自定义缓存

如果想自己实现缓存,可以新建一个Java类,实现DeploymentCache接口,该接口主要有增、删、改、查方法,用于操作缓存数据。下面代码实现了DeploymentCache接口, 提供了最简单的缓存实现。

public class MyCacheBean<T> implements DeploymentCache<T> {public Map<String, T> cache;public MyCacheBean() {cache = new HashMap<String, T>();}public T get(String id) {return cache.get(id);}public boolean contains(String id) {return cache.containsKey(id);}public void add(String id, T object) {cache.put(id, object);}public void remove(String id) {cache.remove(id);}public void clear() {cache.clear();}

代码清单中的 MyCacheBean类,主要维护一个Map对象,各个方法均对Map进行操作。实现了自定义缓存后,要告诉Activiti有这么一个类,为此修改流程引擎配置文件,具体如代码清单3-18所示:

<bean id="processEngineConfiguration"class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"><property name="jdbcUrl" value="jdbc:mysql://localhost:3306/act" /><property name="jdbcDriver" value="com.mysql.jdbc.Driver" /><property name="jdbcUsername" value="root" /><property name="jdbcPassword" value="123456" /><property name="databaseSchemaUpdate" value="true" /><property name="processDefinitionCache"><ref bean="myCache" /></property></bean>
<bean id="myCache" class="org.crazyit.activiti.MyCacheBean"></bean>

在流程配置文件中,定义了myCache的bean,实现类为代码清单 3-17 中的 MyCacheBean, 并在流程引擎配置的 bean 中加入 processDefinitionCache 属性,引用 myCache 的 bean。接下来3-19编写测试代码。

public static void main(String[] args) {// 创建流程引擎ProcessEngineConfigurationImpl config =(ProcessEngineConfigurationImpl) ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("my-cache.cfg.xml");ProcessEngine engine = config.buildProcessEngine();     // 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 进行10次部署for (int i = 0; i < 10; i++) {repositoryService.createDeployment().addClasspathResource("bpmn/default-cache.bpmn").name("dep_" + i).key("key_" + i).deploy();}// 获取缓存MyCacheBean cache = (MyCacheBean) config.getProcessDefinitionCache();// 遍历缓存,输出Map中的keyfor (Iterator it = cache.cache.keySet().iterator(); it.hasNext();) {String key = (String) it.next();System.out.println(key);}}

代码清单3-19 中,将获取的缓存对象强制转换为MyCacheBean,遍历该实例的Map属性并输出key,运行该代码清单可看到结果。

3.3、流程定义权限

对于一个用户或者用户组是否能操作(查看)某个流程定义的数据,需要进行相应的流程定义权限设置。在Activiti中,并没有对流程定义的权限进行检查,而是提供一种反向的方法,让调用者去管理这些权限数据,然后提供相应的API让使用人决定哪种数据可以被查询。

3.3.1、设置流程定义的用户权限

在默认情况下,所有用户(用户组)均可以根据流程定义创建流程实例(启动流程),可以使用Activiti提供的API来设置启动流程的权限数据(设置启动流程的候选用户)。Activiti 提供了设置流程权限数据的API,可以调用这些API来管理这些权限数据,但是,需要注意的是,Activiti本身并不会对权限进行检查,而是提供了相应的权限查询接口,让开发者决定展示何种数据给使用者,从而达到权限控制的作用。例如提供了根据用户来获取其有权限的流程定义,根据流程定义获取有权限的用户组或者用户。 Activiti 的这种设计,使得在开发流程权限功能时开发人员可以有更灵活的选择。

RepositoryService中提供了addCandidateStarterUser方法,给流程定义与用户绑定权限,该方法实际上是向一个中间表中加入数据,表示流程与用户之间的关系。addCandidateStarterUser方法的第一个参数为流程定义ID, 第二个参数为用户的ID。 代码清单 3-20 为一个使用 addCandidateStarterUser 方法的示例:

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 得到身份服务组件IdentityService identityService = engine.getIdentityService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/candidateUser.bpmn").deploy();// 查询流程定义实体ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 写入用户数据creatUser(identityService, "user1", "angus", "young", "abc@163.com","123");creatUser(identityService, "user2", "angus2", "young2", "abc@163.com","123");creatUser(identityService, "user3", "angus3", "young3", "abc@163.com","123");// 设置用户组与流程定义的关系(设置权限)repositoryService.addCandidateStarterUser(def.getId(), "user1");repositoryService.addCandidateStarterUser(def.getId(), "user2");}// 创建用户方法往上

代码清单2-20中,调用RepositoryService的addCandidateStarterUser方法绑定用户与流程定义的权限关系,运行代码清单3-20, 可以看到 ACT_RU_IDENTITYLINK表的数据。
ACT_RU_IDENTITYLINK表用于保存流程中用户组、用户这些身份数据与流程中各个元素之间的关系,这些流程元素包括流程定义、流程任务等。在此需要再次强调,即使该表中存在权限数据,Activiti也不会对这些权限进行拦截或者检查,只会根据这些关系与查询条件,返回相应的符合要求的数据。

3.3.2、设置流程定义的用户组权限

与设置用户的流程定义权限一样,RepositoryService提供了addCandidateStarterGroup方法,使用该方法可以将流程定义与用户组进行权限绑定 ,权限绑定数据同样被写入ACT_ RU_ IDENTITYLINK表中。addCandidateStarterGroup方法的第一个参数为流程定义ID, 第二个参数为用户组ID,以下代码调用了addCandidateStarterGroup方法绑定流程定义权限。

repositoryService.addCandidateStarterGroup(def.getId(),"group1");

addCandidateStarterGroup 方法的使用方法和结果与 addCandidateStarterUser 方法类似, 在 此不再赘述。
注意:有关查询用户纽(用户)的候选流程定义或者根据流程定义查询候选用户纽 (用户)的内容,在 3.3.4 节讲述。

3.3.3、IdentityLink对象

一个IdentityLink实例表示一种身份数据与流程数据绑定的关系,此处所说的身份数据包括用户组和用户数据,流程数据包括流程定义、流程任务等数据。IdentityLink是一个接口,与其他数据库映射实体一样,其对应的实现类为IdentityLinkEntityImpl,对应的数据表为ACT_RU_IDENTITYLINK。 它包含以下映射的属性: 
id:主键,对应由字段。
type:数据类型,对应TYPE_字段,Activiti 为该字段提供了5 个值,分别为 assignee、candidate、starter、participant和owner。本章中为用户或者用户组绑定流程定义时,该值为candidate,表示创建流程实例的请求者。
groupld:绑定关系中的用户组ID,对应GROUP_ID_字段。
userld:绑定关系中的用户ID,对应USER_ID_字段。
taskld: 绑定关系中的流程任务ID,对应TASK_ID_字段。
processDefld:绑定关系中的流程定义ID,对应PROC_DEF_ID_字段。在此需要注意的是,ACT_RU_ IDENTITYLINK表中还有一个REV_字段,IdentityLinkEntitylmpl并没有为该字段做相应的属性映射,在实体与数据表映射的配置文件中,该字段的值被设置为1。

3.3.4、查询权限数据

前面介绍了如何设置流程定义的权限,那么当需要使用这些权限数据时,则可以使用IdentityService中提供的获取流程角色权限数据的方法来获取这些数据,例如根据用户获取该用户有权限启动的流程定义,根据流程定义获取有权限申请的用户和用户组数据。这些获取权限数据的方法分布在各个不同的服务组件中,如果要根据流程定义获取有权限申请的角色数据,则需要使用 IdentityService:如果要根据用户或者用户组得到相应的流程定义数据,则需要使用RepositoryService。在代码清单 3-21 中, 查询流程定义相应的权限数据。

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 得到身份服务组件IdentityService identityService = engine.getIdentityService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/candidateQuery.bpmn").deploy();// 添加2个用户creatUser(identityService, "user1", "张三", "张三", "mail1", "123");creatUser(identityService, "user2", "李四", "李四", "mail2", "123");// 添加2个用户组createGroup(identityService, "group1", "经理组", "manager");createGroup(identityService, "group2", "员工组", "employee");// 查询流程定义ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 设置权限数据repositoryService.addCandidateStarterGroup(def.getId(), "group1");repositoryService.addCandidateStarterGroup(def.getId(), "group2");repositoryService.addCandidateStarterUser(def.getId(), "user1");repositoryService.addCandidateStarterUser(def.getId(), "user2");// 根据用户查询用权限的流程定义List<ProcessDefinition> defs = repositoryService.createProcessDefinitionQuery().startableByUser("user1").list();System.out.println("用户张三有权限的流程定义为:");// 结果为1for (ProcessDefinition dft : defs) {System.out.println("   " + dft.getName());}// 根据流程定义查询用户组数据List<Group> groups = identityService.createGroupQuery().potentialStarter(def.getId()).list();System.out.println("以下用户组对流程定义有权限:");for (Group group : groups) {System.out.println("   " + group.getName());}// 根据流程定义查询用户数据List<User> users = identityService.createUserQuery().potentialStarter(def.getId()).list();System.out.println("以下用户对流程定义有权限:");// 结果为2for (User user : users) {System.out.println("   " + user.getFirstName());}// 根据流程定义查询全部的 IdentityLink(ACT_RU_IDENTITYLINK表) 数据List<IdentityLink> links = repositoryService.getIdentityLinksForProcessDefinition(def.getId());System.out.println("与流程定义相关的数据量: " + links.size());// 结果为4}

在代码清单3-21中,先添加了两个用户组与两个用户,然后为部署的流程定义绑定这 4条身份数据,最后调用代码清单3-21中的代码进行权限数据查询,代码清单3-21中的代码涉及4个方法。
ProcessDefinitionQuery 的 startableByUser 方法:该方法用于根据用户ID查询该用户有权限启动的流程定义数据。
GroupQuery 的 potentialStarter方法:根据流程定义ID查询有权限启动的用户组数据。
UserQuery 的 potentialStarter 方法:根据流程定义ID查询有权限启动的用户数据。
RepositoryService 的 getldentityLinksForProcessDefinition方法:根据流程定义ID查询与之相关的全部权限数据。
运行代码清单3-21,可以看到最终输出结果为:

用户张三有权限的流程定义为:
vacation
以下用户组对流程定义有权限:
经理组
员工组
以下用户对流程定义有权限:
张三
李四
与流程定义相关的数据量:4

注意:ProcessDefinitionQuery的使用方法请参看本章关于 RepositoryService数据查询的讲述,GroupQuery 和 UserQuery 的使用方法请参看本书第 2 章的相关讲述。
由此可见,Activiti这种开放的设计提供了各种查询权限元素的方法,而在它的内部并没有对这些权限数据进行任何的拦截处理。

3.4、RepositoryService数据查询与删除

在本章的开始,使用DeploymentBuilder对象将流程描述文件、流程资源等文件保存到数据库中,本节将讲述如何使用 RepositoryService提供的方法管理这些资源数据,这些资源包括部署的文件、流程描述文件、流程图文件等。第 2章讲过, Activiti 的每种数据都有相应的 Query 查询对象,同样,本章所涉及的部署数据、流程定义数据也有相应的查询对象。下面将讲述这些对象的使用方法。

3.4.1、查询部署资源

部署资源包括与部署相关的文件、流程描述文件、流程图等,可以使用 DeploymentBuilder 的 addXXX 方法将相关的资源文件保存到数据库中,如果需要使用这些文件,则可以调用 RepositoryService 的 getResourceAsStream 方法,只需要提供部署(Deployment) ID 和资源名 称,就可以返回资源的输入流对象。代码清单 3-22 示范了如何使用 getResourceAsStream 方法获取资源。

public static void main(String[] args) throws Exception {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务对象RepositoryService repositoryService = engine.getRepositoryService();// 部署一份txt文件Deployment dep = repositoryService.createDeployment().addClasspathResource("artifact/GetResource.txt").deploy();// 查询资源文件InputStream is = repositoryService.getResourceAsStream(dep.getId(), "artifact/GetResource.txt");// 读取输入流int count = is.available();byte[] contents = new byte[count];is.read(contents);String result = new String(contents);//输入结果System.out.println(result);}

在代码清单 3-22 中,将一个 txt 文件使用 addClasspathResource 方法部署到数据库中, 最后得到一个 Deployment 实例,在此过程中 ,数据库的资源表中会写入一条资源数据(详细见3.1 节)。代码清单 3-22 中,调用getResourceAsStream方法获取该资源文件的输入流,在程序的最后分析这个输入流实例,最终输出部署文件“artifact/GetResource.txt” 的内容。

3.4.2、查询流程文件

在3.2.2节讲过,部署流程描述文件的时候,Activiti 会向资源表中写入该文件的内容,即先将其看作一份普通的文件进行解析与数据保存,然后再解析它的文件内容,并生成相应的流程定义数据。与查询部署资源的 getResourceAsStream 方法一样, RepositoryService 提供了一 个 getProcessModel 方法,调用该方法只需要提供流程定义的 ID即可返回流程文件的 InputStream 实例。代码清单 3-23 为一个调用 getProcessModel 方法的示例。

public static void main(String[] args) throws Exception {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务对象RepositoryService repositoryService = engine.getRepositoryService();// 部署一份txt文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/getProcessModel.bpmn").deploy();// 查询流程定义//查询流程定义实体ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 查询资源文件InputStream is = repositoryService.getProcessModel(def.getId());// 读取输入流int count = is.available();byte[] contents = new byte[count];is.read(contents);String result = new String(contents);//输入结果System.out.println(result);}

代码清单 3-23 中的粗体字代码调用 getProcessModel 方法得到流程描述文件的输入流,最后解析该输入流实例。

3.4.3、查询流程图

在部署一个流程的时候,可以由 Activiti 生成流程图,也可以由我们提供,可以使用 RepositoryService 的 getProcessDiagram 方法返回该流程图的 InputStream 对象 。调用 getProcessDiagram 方法同样需要提供流程定义ID, 根据流程定义数据得到部署数据 ID 与部署资源名称,再到资源表中查询相应的文件数据返回。代码清单 3-24 示范了调用 getProcessDiagram 方法获取流程图的方法。

public static void main(String[] args) throws Exception {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务对象RepositoryService repositoryService = engine.getRepositoryService();// 部署一份流程文件与相应的流程图文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/getProcessDiagram.bpmn").addClasspathResource("bpmn/getProcessDiagram.png").deploy();// 查询流程定义ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 查询资源文件InputStream is = repositoryService.getProcessDiagram(def.getId());// 将输入流转换为图片对象   BufferedImage image = ImageIO.read(is);// 保存为图片文件File file = new File("resource/artifact/result.png");if (!file.exists()) file.createNewFile();FileOutputStream fos = new FileOutputStream(file);ImageIO.write(image, "png", fos);fos.close();is.close();}

在代码清单 3-24 中, 将一份 getProcessDiagram.bpmn 流程文件与它的流程图文件部署到数据库中,然后调用 getProcessDiagram 方法得到流程图的输入流,最后将该输入流以文件的 形式保存到 resource/artifact 目录下,文件名为 result.png。可以查看 bpmn/getProcessDiagram.png 与 result.png, 实际上它们为同一份图片文件(相同的内容)。

3.4.4、查询部署资源名称

查询一次部署所涉及的全部文件名称,可以调用 RepositoryService 的 getDeploymentResourceNames 方法。 代码清单 3-25 为个使用该方法的示例。

public static void main(String[] args) throws Exception {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务对象RepositoryService repositoryService = engine.getRepositoryService();// 部署一份流程文件与相应的流程图文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/GetResourceNames.bpmn").addClasspathResource("bpmn/GetResourceNames.png").deploy();// 查询资源文件名称集合List<String> names = repositoryService.getDeploymentResourceNames(dep.getId());for (String name : names) {System.out.println(name);}}

运行代码输出结果如下

bpmn/GetResourceNames.bpmn
bpmn/GetResourceNames.png

3.4.5、删除部署资源

在前面章节,使用了 RepositoryService 的各个方法来查询部署所产生的相关资源数据。 如果需要删除这些数据,则可以使用 RepositoryService 提供的两个删除方法,这两个方法的描述 如下。
deleteDeployment(String deploymentld):删除部署数据,不进行级联删除,这里所说的级联删除,是指与该部署相关的流程实例数据的删除。
deleteDeployment(String deploymentld, boolean cascade):是否进行级联删除,由调用者决定。 如果 cascade 参数为 false,效果等同于 deleteDeployment(String deploymentld)方 法:如果 cascade 为 true,则会删除部署相关的流程实例数据。 不管删除部署数据时是否指定级联删除,部署的相关数据均会被删除,包括身份数据 ( IdentityLink )、流程定义数据( ProcessDefinition )、流程资源( Resource )与部署数据 (Deployment)。 代码清单 3-26 示范了如何调用两个删除部署数据的方法。
注意:如果设置为级联删除,则会删除流程实例数据( Processlnstance ),其中流程 实例数据也包括流程任务( Task )与流程实例的历史数据;如果设置为 false, 不进行级联删除的话,如果 Activiti 数据库中已经存在流程实例数据,那么将会删除失败,因为在删除流程程定义时,流程定义数据的ID已经被流程实例的相关数据所引用 。

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务对象RepositoryService repositoryService = engine.getRepositoryService();// 部署一份流程文件与相应的流程图文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/deleteDeployment.bpmn").deploy();// 查询流程定义ProcessDefinition def = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 开始流程engine.getRuntimeService().startProcessInstanceById(def.getId());try {// 删除部署数据失败,此时将会抛出异常,由于cascade为falserepositoryService.deleteDeployment(dep.getId());} catch (Exception e) {System.out.println("删除失败,流程开始,没有设置cascade为true");}System.out.println("============分隔线");// 成功删除部署数据repositoryService.deleteDeployment(dep.getId(), true);}

在代码清单3-26 中,调用 deleteDeployment(String deploymentld)方法删除部署数据失败(抛出数据库异常),因为该方法不进行级联删除。在代码清单 3-26 中,己经启动了流程,这意味着已经产生了相关的流程实例数据,而调用 deleteDeployment(String deploymentld, boolean cascade)方法时将 cascade 设置为 true,所以即使流程己经启动, 该方法 也会将流程实例相关的全部数据删除,因此最终成功删除部署数据。

3.4.6、DeploymentQuery对象

Activiti 中的每个查询对象(XXXQuery)均有自己相应的查询方法和排序方法 (详细请见 第2 章〉,本章的 DeploymentQuery 对象包括如下方法。
deploymentld(String id):添加 ID 查询条件, 查询ID为参数值的数据记录。
deploymentName(String name): 添加名称查询条件,查询 Deployment 名称为参数值的数据记录。deploymentNameLike(String name): 添加模糊查询条件,查询 Deployment 名称含有参 数值的数据记录。
orderByDeploymentld:设置查询结果按照 Deployment 的田进行排序,排序方式由 asc 与 desc 方法决定。
orderByDeploymentTime: 设置查询结果按照 DEPLOY_TIME_字段排序。
orderByDeploymentName: 设置查询结果按照 Deployment 名称排序。 
注意:各个 Query 对象提供的所谓查询和排序方法,实际上是为本次查询添加查询条件,最终执行查询可由 list 和 singleResult 方法进行。
在代码清单 3-27 中使用了 DeploymentQuery 的三个查询方法。

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 得到流程存储服务实例RepositoryService repositoryService = engine.getRepositoryService();// 写入5条Deployment数据Deployment depA = repositoryService.createDeployment().addString("a1", "a1").addString("a2", "a2").addString("a3", "a3").name("a").deploy();Deployment depB = repositoryService.createDeployment().addString("b1", "b1").addString("b2", "b2").addString("b3", "b3").name("b").deploy();Deployment depC = repositoryService.createDeployment().addString("c1", "c1").addString("c2", "c2").addString("c3", "c3").name("c").deploy();Deployment depD = repositoryService.createDeployment().addString("d1", "d1").addString("d2", "d2").addString("d3", "d3").name("da").deploy();Deployment depE = repositoryService.createDeployment().addString("e1", "e1").addString("e2", "e2").addString("e3", "e3").name("eb").deploy();//deploymentId方法Deployment depAQuery = repositoryService.createDeploymentQuery().deploymentId(depA.getId()).singleResult();System.out.println("根据id查询:" + depAQuery.getName());//deploymentName方法Deployment depBQuery = repositoryService.createDeploymentQuery().deploymentName("b").singleResult();System.out.println("查询名称为b:" + depBQuery.getName());//deploymentNameLike, 模糊查询,结果集为2List<Deployment> depCQuery = repositoryService.createDeploymentQuery().deploymentNameLike("%b%").list();System.out.println("模糊查询b,结果数量:" + depCQuery.size());}

在代码清单 3-27 中使用了 三个查询方法,其中需要注意的是,模糊查询方法 deploymentNameLike 的参数中需要加入通配符,就像使用 MySQL 的 LIKE 关键字一样。 DeploymentQuery 的其他三个设置排序条件的方法,在此不再赘述。运行代码清单 3-27,输出 结果如下:

根据id查询:a
查询名称为b:b
模糊查询b,结果数量2

3.4.7、ProcessDefinitionQuery对象

ProcessDefinitionQuery 对象与 DeploymentQuery 对象类似,是 RepositoryService 提供的另 外一个数据查询对象,该对象主要用于查询流程定义数据。 ProcessDefinitionQuery 对象提供了 若干个根据字段查询与字段排序的方法,在此需要注意的是,在 3.3.4 节,使用了该对象的 startableByUser(String userld)方法,该方法根据用户ID返回该用户有权限启动流程定义的流程定义数据 。另外,ProcessDefinitionQuery 中有一个 messageEventSubscriptionName(String messageName)方法,该方法的使用将在讲述 BPMN 2.0 规范的相关章节中讲解 。ProcessDefinitionQuery 中的其他根据字段查询、排序的方法,本章不再赘述,读者可参照 DeploymentQuery 的使用方法,测试这些方法的作用。

3.5、小结

本章主要讲述了流程存储服务接口(RepositoryService)的使用方法、流程存储相关数据的管理,这些流程存储数据包括部署数据、部署的文件资源、流程定义、流程定义的身份关联 数据等。本章的核心在于向读者讲解如何使用 RepositoryService 的 API 对这些数据进行管理, 本章讲述了 RepositoryService 的大部分 API,其他的 API 会在后面相关章节讲解。在了解这些 API 的过程中,能掌握到 Activiti 的一些数据库设计、程序设计方面的知识。流程的存储是流 程控制与流程制定的基础,熟悉并且灵活使用这些 API, 将会为开发工作流应用提供极大的便利。

4、流程任务管理

在第3章中,讲述了如何使用 RepositoryService 进行流程部署管理、流程定义管理,Process Engine 提供了一系列的业务组件供开发者使用。本章将讲解任务服务组件 TaskService 的使用。在 Activiti 中,一个 Task 实例表示流程中的一个任务,任务类型多种多样,例如用户任务、脚本任务等,关于这些类型将在讲述 BPMN 规范的章节中讲解。 TaskService 提供了许多操作流程任务的 API,包括任务的查询、创建与删除、权限设置和参数设置等,本章将详细 讲解这些 API 的使用以及在使用过程中的注意事项。在前面章节中,讲述了 ProcessEngine 对象,我们知道,得到 ProcessEngine 的实例后,可 以调用 getTaskService 方法来获取 TaskService 实例。

4.1、任务的创建与删除

一般情况下,可以通过定义流程描述 XML 文件来定义一个任务,Activiti 在解析该文件时,会将任务写到对应的数据表(ACT_RU_TASK)中 。在此过程中,创建任务的工作己由 Acitiviti完成了。如果需要使用任务数据,则可以调用相应查询的 API 查询任务数据并且进行相应的设置。下面将讲解如何使用 XML 文件定义任务,以及如何使用 TaskService 提供的 API 来保存和删除任务数据。

4.1.1、 Task接口

一个 Task 实例表示流程中的一个任务,与其他实例一样,Task 是一个接口,并且遵守数据映射实体的命名规范。Task 的实现类为TaskEntitylmpl,对应的数据库表为ACT_RU_TASK。TaskEntitylmpl 包括以下映射属性。
id: 主键,对应 ID字段。
revision: 该数据版本号,对应REV_字段。
owner:任务拥有人,对应 OWNER_字段。
assignee:被指定需要执行任务的人,对应 ASSIGNEE_字段。
delegationState:任务被委派的状态,对应 DELEGATION_字段。
parentTaskld:父任务的 ID (如果本身是子任务的话),对应 PARENT_TASK_ ID_字段。
name:任务名称,对应 NAME_字段。
description:任务的描述信息,对应 DESCRIPTION_字段。
priority:任务的优先级,默认值为 50,表示正常状态,对应 PRIORITY_段。
createTime: 任务创建时间,对应 CREATE_TIME_ 字段。
dueDate:预订日期,对应 DUEDATE_字段。
executionld:该任务对应的执行流ID,对应EXECUTION_ID_ 字段。
processDefinitionld:任务对应的流程定义ID,对应 PROC_DEF_ID_字段。
claimTime:任务的提醒时间,对应 CLAIM_TIME_字段。 
TaskEntitylmpl 中的一些与流程控制相关的字’段(例如执行流与流程实例等),将在讲述流程控制的相关章节详细讲解。

4.1.2、创建与保存Task实例

与创建用户组实例(Group)、用户实例(User)一样, TaskService 提供了创建 Task 实例的方法。调用 TaskService 的 newTask()与 newTask(String taskld)方法,可以获取一个 Task 实例,开发人员不需要关心 Task 的创建细节。调用这两个创建 Task 实例的方法时, TaskService 会初 始化 Task 的部分属性,这些属性包括 taskld、创建时间等。创建了 Task 实例后,如果需要将其保存到数据库中,则可以使用 TaskService 的 saveTask (Task task)方法,如果保存的 Task 实例有ID值,则会使用该值作为 Task 数据的主键, 没有的话,则由 Activiti 为其生成主键。代码清单 4-1 示范了如何调用 newTask 与 saveTask 方法。

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个Task,不设置IDTask task1 = taskService.newTask();taskService.saveTask(task1);//保存第二个Task,设置IDTask task2 = taskService.newTask("审核任务");taskService.saveTask(task2);}

在代码清单 4-1 中,保存了两个 Task 实例,一个调用 newTask()方法创建,另外一个调用 newTask(String taskld)方法创建,执行代码清单 4-1 后,可以打开数据库的 ACT_RU_ TASK 表 查看结果。一般情况下,Activiti 会解析流程描述文件并且将相应的 Task 保存到数据库中,如果提供了流程描述文件,开发人员就不需要关心 Task 的创建过程。

8.1.3、删除任务

TaskService 提供了多个删除任务的方法,包括删除单个任务、删除多个任务的方法。 这些删除方法与 3.4.5 节中讲述的删除部署资源的方法类似,同样由开发人员决定是否进行级联删除。这些删除方法描述如下。
deleteTask(String taskld):根据 Task 的ID删除 Task 数据,调用该方法不会进行级联删除。
deleteTask(String taskld, boolean cascade):根据 Task 的 ID 删除 Task 数据,由调用者决 定是否进行级联删除。
deleteTasks(Collection<String> tasklds):提供多个 Task 的 ID进行多条数据删除,调用该方法不会进行级联删除。
deleteTasks(Collection<String> tasklds, boolean cascade):提供多个 Task 的ID进行多条 数据删除,由调用者决定是否进行级联删除。删除任务时,将会删除该任务下面全部的子任务和该任务本身,如果设置了进行级联删除,则会删除与该任务相关的全部历史数据(ACT_HI_TASKINST 表)和子任务,如果不进行级联删除,则会使用历史数据将该任务设置为结束状态。除此之外,如果尝试删除一条不存的任务数据(提供不存在的 taskld),此时 deleteTask 方法会到历史数据表中查询是否存在该任务相关的历史数据,如果存在则删除,不存在则忽略。在代码清单 4-2 中调用了删除任务的 4 个方法。

public static void main(String[] args) {// 创建流程引擎ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();//获取任务服务组件TaskService taskService = engine.getTaskService();// 保存若干个Taskfor (int i = 1; i < 10; i++) {saveTask(taskService, String.valueOf(i));}    // 删除task(不包括历史数据和子任务)taskService.deleteTask("1");// 删除task(包括历史数据和子任务)taskService.deleteTask("2", true);// 删除多个task(不包括历史数据和子任务)List<String> ids = new ArrayList<String>();ids.add("3");ids.add("4");taskService.deleteTasks(ids);//删除多个task(包括历史数据和子任务)ids = new ArrayList<String>();ids.add("5");ids.add("6");taskService.deleteTasks(ids, true);// 再删除ID为3的task,此时3的历史数据也会被删除(如果有的话)taskService.deleteTask("3", true);}//保存一个taskstatic void saveTask(TaskService taskService, String id) {Task task1 = taskService.newTask(id);taskService.saveTask(task1);}

在代码清单 4-2 中,先往数据库中写入 9 条 Task 数据,然后分别调用 4 个删除 Task 的方法对这些 Task 数据进行删除。需要注意的是,调用完 4 个删 除方法后,最后还删除了ID为 3 的 Task 数据。此时 ID 为 3 的 Task 数据己经被删除(历史数据还在),那么最后执行删除时, ID 为 3 的历史数据也会同样被删除,到最后,数据库中剩下的没有被删除的 Task 数据。
由于在删除数据 1 的时候,并没有设置级联删除,因此ID 为 1 的 Task 历史数据仍然存在于 ACT_HI_TASKINST 表中。 除此之外,删除一条 Task 数据,不管是否设置为级联删除,都会将其关联的 Task 权限数据与参数数据删除。关于 Task 的权限与参数设置,将在下面讲解。

4.2、任务权限

Activiti 提供了设置任务权限数据的 API, 通过调用这些 API,可以为任务设置角色的权限数据,这些角色包括用户组与用户。在 3.3 节中,讲解了流程定义的权限, Activiti 对流程定义的权限不做任何的控制,只会提供相应的流程定义查询方法,将相应的流程定义数据展现 给不同的角色。任务的权限同样使用这种设计, Activiti 不会对权限进行拦截,只提供查询的 API 让开发人员使用。

4.2.1、设置候选用户组

根据前面章节的讲述可知,流程定义与用户组(或者用户)之间的权限数据,通过一个 ACT_ RU_ IDENTITYLINK 中间表来保存,该表对应的实体为 IdentityLink 对象(详情请见 3.3.3 节)。 任务的权限数据设置与之类似,也是使用 ACT_RU_IDENTITYLINK 表来保存这些权限数据,因此在调用设置流程权限 API 时, Activiti 最终会往这个表中写入数据。 代码清单 4-3 设置了任务的候选用户组。

public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户组Group groupA = createGroup(identityService, "group1", "经理组", "manager");// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask("task1");taskService.saveTask(task1);//保存第二个TaskTask task2 = taskService.newTask("task2");taskService.saveTask(task2);//绑定用户组和任务关系taskService.addCandidateGroup("task1", groupA.getId());taskService.addCandidateGroup("task2", groupA.getId());}// 将用户组数据保存到数据库中static Group createGroup(IdentityService identityService, String id,String name, String type) {// 调用newGroup方法创建Group实例Group group = identityService.newGroup(id);group.setName(name);group.setType(type);identityService.saveGroup(group);return identityService.createGroupQuery().groupId(id).singleResult();}

在代码清单 4-3 中,新建一个用户组,再新建两个 Task 并保存到数据库中,最后调用 TaskService 的 addCandidateGroup 方法绑定任务与用户组之间的关系。
 ACT_RU_IDENTITYLINK 表中被加入了两条数据,其中 TASK_ID字段为相应任务数据的 ID, GROUP_ID 为用户组数据的 ID,另外, TYPE 字段值为“cadidate”, 表示该任务为控制请求的权限数据。 在使用 addCandidateGroup 方法时,需要注意的是,如果给出用户组 ID 为 null,那么将会抛出异常,异常信息为: userld and groupld cannot both be null:如果给出的任务 ID 对应的数据不存在,同样也会抛出异常,异常信息如下: Cannot find task with id (ID)。

4.2.2、设置候选用户

候选用户是一 “群”将会拥有或者执行任务权限的人,但是任务只允许一个人执行或者拥有,而任务的候选人则是指一个用户群体。TaskService 同样提供了一个设置用户权限数据的方法 addCandidateUser,与 addCandidateGroup 方法类似,调用该方法需要提供用户ID与任务ID,执行该方法后,会向 ACT_RU_IDENTITYLINK 表中写入相应的权限数据。在代码清单 4-4 中调用了 addCandidateUser 方法来设置任务的候选人。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户User user = creatUser(identityService, UUID.randomUUID().toString(),"张三", "lastname", "abc@163.com", "123");User user2 = creatUser(identityService, UUID.randomUUID().toString(),"李四", "lastname", "abc@163.com", "123");// 获取任务服务组件TaskService taskService = engine.getTaskService();// 保存一个TaskTask task1 = taskService.newTask("task1");taskService.saveTask(task1);// 绑定用户与任务关系taskService.addCandidateUser("task1", user.getId());taskService.addCandidateUser("task1", user2.getId());}// 创建用户方法往前

4.2.3、权限数据查询

如果需要查询用户组和用户的候选任务,可以使用 TaskService 的 createTaskQuery 方法, 得到 Task对应的查询对象。TaskQuery 中提供了 taskCandidateGroup和 taskCandidateUser方法, 这两个方法可以根据用户组或者用户的ID查询候选 Task 的数据。与流程定义一样, 这些查询方法会先到权限数据表中查询与用户组或者用户关联了的数据, 查询得到 taskld 后,再到 Task 中查询任务数据并且返回。如果得到了任务的 ID,想查询相应的关系数据,则可以调用 TaskService 的 getldentityLinksForTask 方法,该方法根据任务ID查询 IdentityLink 集合。 在代码清单4-5 中调用了以上所述方法查询相应的 Task 数据和 IdentityLink 数据。

 public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户User user = creatUser(identityService, "user1", "张三", "last", "abc@163.com", "123");// 新建用户组Group groupA = createGroup(identityService, "group1", "经理组", "manager");Group groupB = createGroup(identityService, "group2", "员工组", "employee");// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask("task1");task1.setName("申请假期");taskService.saveTask(task1);//保存第二个TaskTask task2 = taskService.newTask("task2");task2.setName("审批假期");taskService.saveTask(task2);//绑定权限taskService.addCandidateGroup("task1", groupA.getId());taskService.addCandidateGroup("task2", groupB.getId());taskService.addCandidateUser("task2", user.getId());     //根据用户组查询任务List<Task> tasks = taskService.createTaskQuery().taskCandidateGroup(groupA.getId()).list();System.out.println("经理组的候选任务有:");for (Task task : tasks) {System.out.println("   " + task.getName());}     //根据用户查询任务tasks = taskService.createTaskQuery().taskCandidateUser(user.getId()).list();System.out.println("张三的候选任务有");for (Task task : tasks) {System.out.println("   " + task.getName());}//调用taskCandidateGroupInList<String> groupIds = new ArrayList<String>();groupIds.add(groupA.getId());groupIds.add(groupB.getId());tasks = taskService.createTaskQuery().taskCandidateGroupIn(groupIds).list();System.out.println("经理组与员工组的任务有:");for (Task task : tasks) {System.out.println("   " + task.getName());}//查询权限数据List<IdentityLink> links = taskService.getIdentityLinksForTask(tasks.get(0).getId());System.out.println("关系数据量: " + links.size());}// 将用户组数据保存到数据库中static Group createGroup(IdentityService identityService, String id,String name, String type) {// 调用newGroup方法创建Group实例Group group = identityService.newGroup(id);group.setName(name);group.setType(type);identityService.saveGroup(group);return identityService.createGroupQuery().groupId(id).singleResult();}//创建用户方法

在代码清单 4-5 中,调用了 taskCandidateGroup 和 taskCandidateUser 方法, 来根据用户组 和用户查询相应的流程。除了调用这两个方法外,代码清单2-5还调用了 taskCandidateGroupln 方法。该方法用于查询若干个用户组有权限的任务,如果一 个任务同时被设置到不同的用户组下,那么调用该方法时传入多个用户组 ID,但最终只会返 回一个任务对象。在代码清单 4-5 的最后还调用了 getIdentityLinksForTask 方法,用于查询权 限关联数据。运行代码清单 4-5,输出结果如下。

经理组拥有权限的任务有:申请请假
张三拥有权限的任务有:审批假期
经理组与员工组的任务有:申请假期审批假期
权限数据量:1

注意:在讲解流程定义时讲过, 用户组与用户的 Query 对象均提供了根据流程定义ID 查询用户组与用户数据的方法,但是并没有提供根据任务ID查询用户组或者用户的方法。

4.2.4、设置任务持有人

TaskService 中提供了一个 setOwner 方法来设置任务的持有人,调用该方法后,会设置流程表的 OWNER_字段为相应用户的 ID。 如果想根据用户查询其所拥有的任务,可以调用 TaskQuery 的 taskOwner 方法。 代码清单 4-6 即是一个调用 setOwner 方法来设置任务持有人的示例。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户User user = creatUser(identityService, "user1", "张三", "last","abc@163.com", "123");// 获取任务服务组件TaskService taskService = engine.getTaskService();// 保存一个TaskTask task1 = taskService.newTask("task1 ");task1.setName("申请任务");taskService.saveTask(task1);// 设置任务持有人taskService.setOwner(task1.getId(), user.getId());System.out.println("用户张三持有任务数量:"+ taskService.createTaskQuery().taskOwner(user.getId()).count());}// 创建用户方法往上

代码清单4-6中的代码调用setOwner方法设置任务持有人,调用该方法后,再根据用户id查询该用户所持有的任务。运行代码,输出结果如下:

用户张三持有任务数量:1

4.2.5、设置任务代理人

除设置任务持有人外,TaskService还提供了一个setAssignee方法用于设置任务的代理人,与 setOwner方法一样,setAssignee 方法会改变ACT_RU_TASK 表的 ASSIGNEE_字段值。当需要根据任务代理人查询任务时,可以调用 TaskQuery 的 taskAssignee 方法设定该查询条件。 在代码清单 4-7 中调用了 setAssignee 方法。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户User user = creatUser(identityService, "user1", "张三", "last","abc@163.com", "123");// 获取任务服务组件TaskService taskService = engine.getTaskService();// 保存一个TaskTask task1 = taskService.newTask("task1");task1.setName("申请任务");taskService.saveTask(task1);// 设置任务持有人taskService.setAssignee(task1.getId(), user.getId());System.out.println("用户张三受理的任务数量:"+ taskService.createTaskQuery().taskAssignee(user.getId()).count());}// 创建用户方法往上

代码清单 4-7 中的粗体字代码调用了 setAssignee 方法,运行代码清单 4-7,输出结果如下: 用户张三受理的任务数量: 1

4.2.6 添加任务权限数据

在前面的几节中,讲述了如何设置任务的权限数据,除了这些方法外, TaskService 还提 供了两个添加任务权限数据的方法,这两个方法的描述如下。 
addGroupldentityLink(String taskld, String groupld, String identityLinkType):添加用户组权限数据,第一个参数为任务ID, 第二个参数为用户组ID,第三个参数为权限数据类型标识。
addUserldentityLink(String taskld, String userld, String identityLink:Type):添加用户权限数据,第一个参数为任务ID,第二个参数为用户ID,第三个参数为权限数据类型标识。
在代码清单 4-8 中使用这两个方法添加了任务权限数据。

public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户组Group groupA = createGroup(identityService, "group1", "经理组", "manager");// 新建用户User user = creatUser(identityService, "user1", "张三", "last", "abc@163.com", "123");// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask("task1");task1.setName("申请任务");taskService.saveTask(task1);//调用addGroupIdentityLink方法taskService.addGroupIdentityLink(task1.getId(), groupA.getId(), IdentityLinkType.CANDIDATE);taskService.addGroupIdentityLink(task1.getId(), groupA.getId(), IdentityLinkType.OWNER);taskService.addGroupIdentityLink(task1.getId(), groupA.getId(), IdentityLinkType.ASSIGNEE);//调用addUserIdentityLink方法Task task2 = taskService.newTask("task2");task2.setName("申请任务2");taskService.saveTask(task2);taskService.addUserIdentityLink(task2.getId(), user.getId(), IdentityLinkType.CANDIDATE);taskService.addUserIdentityLink(task2.getId(), user.getId(), IdentityLinkType.OWNER);taskService.addUserIdentityLink(task2.getId(), user.getId(), IdentityLinkType.ASSIGNEE);}//创建用户方法

代码清单 4-8 中的粗体字代码分别调用 addGroupldentityLink 和 addUserldentityLink 方法 三次,分别将第三个参数(权限类型标识)设置为 CANDIDATE、OWNER 和 ASSIGNEE。
使用 addUserldentityLink方法将权限类型标识设置为CANDIDATE,其效果等同于调用 addCandidateUser 方法:将权限类型标识设置为 OWNER,其效果等同于调用 setOwner 方法; 将权限类型标识设置为 ASSIGNEE ,其效果等同于调用 setAssignee 方法。调用 addGroupldentityLink 方法与调用 addUserldentityLink 方法的效果类似,但是需要注意的是,将 用户组设置为任务所有人或者任务代理人,这并不合适,虽然可以成功调用 addGroupldentityLink 方法,但其在删除权限数据时,将会抛出异常。

4.2.7、删除用户组权限

TaskService 中提供了两个方法用于删除用户组的任务权限,这两个方法的描述如下。 
deleteGroupldentityLink(String taskld, String groupld, String identityLinkType): 删除任务 的权限数据,第一个参数为任务ID,第二个参数为用户组ID,第三个参数为任务权限类型标识。
deleteCandidateGroup(String taskld, String groupld):删除任务的候选用户组数据, 第一 个参数为任务ID,第二个参数为用户组ID。 在代码清单 4-9 中使用了删除用户组任务权限的两个方法。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户组Group groupA = createGroup(identityService, "group1", "经理组", "manager");// 获取任务服务组件TaskService taskService = engine.getTaskService();// 保存第一个TaskTask task1 = taskService.newTask("task1");task1.setName("审批任务");taskService.saveTask(task1);// 调用addGroupIdentityLink方法taskService.addGroupIdentityLink(task1.getId(), groupA.getId(),IdentityLinkType.CANDIDATE);taskService.addGroupIdentityLink(task1.getId(), groupA.getId(),IdentityLinkType.OWNER);taskService.addGroupIdentityLink(task1.getId(), groupA.getId(),IdentityLinkType.ASSIGNEE);// 调用delete方法taskService.deleteCandidateGroup(task1.getId(), groupA.getId());// 以下两个s方法将抛出异常taskService.deleteGroupIdentityLink(task1.getId(), groupA.getId(),IdentityLinkType.OWNER);taskService.deleteGroupIdentityLink(task1.getId(), groupA.getId(),IdentityLinkType.ASSIGNEE);}

代码清单 4-9 中的代码分别调用 deleteGroupldentityLink和 deleteCandidateGroup 方法删除任务组的用户权限。在此需要注意的是,如果在调用 deleteGroupldentityLink 方法时传 入的权限类型标识是 OWNER 或者 ASSIGNEE,那么将会抛出异常。 异常信息为: Incompatible usage: cannot use type ’owner' together with a groupld。 如果在调用 addGroupldentityLink 方法时使用的是自定义的权限类型标识,那么在调用 deleteGroupldentityLink 方法进行删除时,要传入相同的权限类型标识才能删除权限数据。

4.2.8、删除用户权限

TaskService 中提供了两个类似的方法用于删除用户的任务权限数据,这两个方法的描述 如下。
deleteCandidateUser(String taskld, String userld):删除任务权限 类 型标 识为 “CANDIDATE”的用户权限数据。
deleteUserldentityLink(String taskld, String userld, String identityLinkType):删除任务权 限类型为 identityLinkType 的用户权限数据。
在使用 addCandidateUser 方法时,会插入一条权限数据到权限中间表中,如果需要删除该权限数据,则可以调用相应的 deleteCandidateUser 方法。除了 addCandidateUser 方法外, 还有 setOwner、 setAssignee 和 addUserldent即Link 方法可以用于添加不同类型的权限数据, 其中 setOwner 和 setAssignee 方法会改变任务表的 OWNER_和 ASSIGNEE_字段值 。在调用 deleteUserldentityLink 方法时,如果传入的类型标识为 OWNER_或者 ASSIGNEE_,那么会将任 务表的 OWNER_和 ASSIGNEE_字段的值设置为 null。 在代码清单 4-10 中调用了删除用户权限的方法。

public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取身份服务组件IdentityService identityService = engine.getIdentityService();// 新建用户User user = creatUser(identityService, "user1", "first", "last", "abc@163.com", "123");// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask("task1");taskService.saveTask(task1);//添加用户权限taskService.addCandidateUser(task1.getId(), user.getId());long count = taskService.createTaskQuery().taskCandidateUser(user.getId()).count();System.out.println("调用addCandidateUser方法后,用户的候选任务数量:" + count);//删除用户权限taskService.deleteCandidateUser(task1.getId(), user.getId());count = taskService.createTaskQuery().taskCandidateUser(user.getId()).count();System.out.println("调用deleteCandidateUser方法后,用户的候选任务数量:" + count);//添加用户权限taskService.addUserIdentityLink(task1.getId(), user.getId(), IdentityLinkType.OWNER);count = taskService.createTaskQuery().taskOwner(user.getId()).count();System.out.println("调用addUserIdentityLink方法后,用户的候选任务数量:" + count);//删除用户权限taskService.deleteUserIdentityLink(task1.getId(), user.getId(), IdentityLinkType.OWNER);count = taskService.createTaskQuery().taskOwner(user.getId()).count();System.out.println("调用deleteUserIdentityLink方法后,用户的候选任务数量:" + count);}//创建用户方法

在代码清单 4-10 中,调用了 deleteCandidateUser 方法来删除权限中间表数据,也调用了 deleteUserldentityLink 方法来删除权限数据。在调用 deleteUserldentityLink 方法时, 如果传入的权限类型标识为 CANDIDATE,那么效果等同于调用 deleteCandidateUser 方法;如果传入权限类型标识为 OWNER 或者 ASSIGNEE, 那么任务表的 OWNER_和 ASSIGNEE_字段的值会被设置为 null。与用户组类似,如果传入的权限类型标识为 自定义类型, 那么删除时也需要提供同样的权限类型标识。
注意:任务的持有用户喝受理用户只能有一个,而任务的候选用户(用户组)可以有多个。

4.3、任务参数

当一个任务被传递到执行人手中时,他需要知道该任务的全部信息,包括任务的基本信息(创建时间、内容等),还需要得到任务的相关参数。 例如一个请假申请, 请假的天数、 开始时间等均为该申请的参数。编写这个请假申请的任务由请假申请人发起,在执行编写请假任务时,就需要设置这一系列的请假任务参数。在 Activiti中,参数类型分为流程参数和任务参数, 下面我们将讲解任务参数的管理。

4.3.1、基本类型参数设置

在 Activiti 数据库设计相关章节中我们讲过, Activiti 中的各种参数均保存在 ACT_RU_VARIABLE 表中,因此当调用了相应的 API 设置参数后,这些参数都会体现在参数数据表中。 Activiti 支持多种参数类型设置,开发者可以根据实际情况设置不同的参数。例如在一个请假流程中,若需要设置天数,可以使用 Integer 类型:需要设置日期,可以使用 Date 类型。设置参数可以调用 TaskService 的 setVariable(String taskld, String variableName, Object value)方法。调用该方法需要传入 taskld、参数名称和参数值,其中参数值类型为 Object,根据传入的参数类型,参数表的 TYPE_字段会记录参数的类型标识,当前 Activiti 支持以下基本参数类型。
  Boolean: 布尔类型,参数类型标识为 boolean。
Date:日期类型,参数类型标识为 date。
Double:双精度类型,参数类型标识为 double。
Integer:整型,参数类型标识为 integer。
Long: 长整型,参数类型标识为 long。
Null: 空值,参数类型标识为 null。
Short: 短整型,参数类型标识为 short。
String: 字符型,参数类型标识为由ing。
在代码清单 4-11 中调用了 setVariable 方法来设置基本参数类型。

public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask("task1");taskService.saveTask(task1);    Date d = new Date();short s = 3;//设置各种基本类型参数taskService.setVariable(task1.getId(), "arg0", false);taskService.setVariable(task1.getId(), "arg1", d);taskService.setVariable(task1.getId(), "arg2", 1.5D);taskService.setVariable(task1.getId(), "arg3", 2);taskService.setVariable(task1.getId(), "arg4", 10L);taskService.setVariable(task1.getId(), "arg5", null);taskService.setVariable(task1.getId(), "arg6", s);taskService.setVariable(task1.getId(), "arg7", "test");}

运行后ACT_RU_VARIABLE 表中的 TYPE 字段被设置为相应的数据类型标识,除双精度类型(Double)、空值类型(Null)和字符类型(String)外,其他基本类型的参数值均被设置到参数表的 LONG_字段,其中整型、长整型、字符型和短整型均被设置到了参数表的 TEXT_字段。 在此需要注意的是,在保存日期类型的参数时,会调用 java.util.Date 类的 getTime 方法获取日期值。 除了这 8 种基本数据类型外,还支持其他的类型,后面会讲述其他 的类型。 
注意:如果设直的参数名称存在重复,那么后面的参数将会覆盖前面设置的参数。

4.3.2、序列化参数

上一节讲解了如何设置基本数据类型的参数, setVariable 方法的第三个参数的类型为 Object,因此在调用该方法时,可以传入自定义对象。如果传入的参数为自定义对象,那么调用 setVariable 方法时,会将该对象进行序列化(被序列化的对象必须实现 Serializable 接口), 然后将其保存到资源表(ACT_GE_BYTEARRAY)中,而参数表为该参数数据做外键关联。 根据第 3章的讲述我们知道,资源表用于保存流程资源或者byte 数组,因此可以推测 setVariable 的实现过程,即该方法会将传入的对象进行序列化,得到 byte 数组后,将其写入数据库中。 代码清单 4-12 为一个设置序列化参数的示例。

public static void main(String[] args) throws Exception {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask(UUID.randomUUID().toString());task1.setName("出差申请");taskService.saveTask(task1);//设置序列化参数taskService.setVariable(task1.getId(), "arg0", new TestVO("crazyit"));TestVO var = (TestVO)taskService.getVariable(task1.getId(), "arg0");System.out.println(var.getName());}public class TestVO implements Serializable {private String name;public TestVO(String name) {this.name = name;}public String getName() {return this.name;}
}

序列化参数的设置与基本类型参数的设置一样,都是直接调用 setVariable 方法, 但是 Test VO 对象必须为序列化对象。 在执行代码清单 4-12 后,数据库中的资源表与参数表的数据 如图 4-6 所示。 由图 4-6 可以看到,参数表中的 BYTEARRAY_ID_ 字段的值为 30001, 表示该参数对应的是资源表ID 为 30001 的数据,并且参数表的 TYPE_字段的值为 serializable。注意资源表的另外一条数据,其是历史数据所关联的资源 id。
将任务的参数都设计为一个对象,这更符合面向对象的思想,但是如果将一些大对象进行 序列化,必然会损耗一定的性能,因此在使用序列化对象作为任务参数时,尽量避免传入大对象。 这里所说的大对象,是指保存了过多信息的对象,例如有一个销售单对象,它有一个集合 属性,里面存放了许多 (例如 100 万条)的销售明细, 那么对这个对象进行序列化时, 必然会 造成性能的损耗。

4.3.3、获取参数

在调用 setVariable 方法后,不管设置的是基本类型的参数还是序列化参数,均可以使用 TaskService 的 getVariable 方法得到任务的参数。调用该方法只需要提供任务ID与参数名称即可,在代码清单 4-13 中就使用了 getVariable 方法来得到任务参数。

public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask("task1");task1.setName("请差申请");taskService.saveTask(task1);//设置各种基本类型参数taskService.setVariable(task1.getId(), "days", 5);taskService.setVariable(task1.getId(), "target", new TestVO("北京"));//获取天数Integer days = (Integer)taskService.getVariable(task1.getId(), "days");System.out.println("出差天数:" + days);//获取目的地TestVO target = (TestVO)taskService.getVariable(task1.getId(), "target");System.out.println("出差目的地:" + target.getName());}

在代码清单 4-13 中, 新建了一个出差申请的任务, 设置出差天数为 5,目标城市为北京, 然后调用 getVariable 方法分别获取 Integer 类型的参数和序列化参数。 需要注意的是,在获取参数时,必须清楚获取的参数类型,否则在强制类型转换时,会抛出类型转换异常。

4.3.4、参数作用域

当任务与流程绑定后,设置的参数均会有其作用域。例如设置一个任务参数,希望在整个流程中均可以使用,那么可以调用 setVariable 方法,如果只希望该参数仅仅在当前这个任务中使用, 那么可以调用 TaskService 的 setVariableLocal 方法。 调用了 setVariable 方法后,如果调 用 getVariableLocal 方法来获取参数,将查找不到任何值,因为 getVariableLocal 方法会查询当前任务的参数,而不会查询整个流程中的全局参数,如代码清单 4-14 所示。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/vacation.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 分别调用setVariable和setVariableLocal方法Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();taskService.setVariable(task.getId(), "days", 10);taskService.setVariableLocal(task.getId(), "target", "欧洲");// 获取参数Object data1 = taskService.getVariable(task.getId(), "days");System.out.println("获取休假天数:" + data1);Object data2 = taskService.getVariable(task.getId(), "target");System.out.println("获取目的地: " + data2);// 获取参数Object data3 = taskService.getVariableLocal(task.getId(), "days");System.out.println("使用getVariableLocal方法获取天数:" + data3);}

在代码清单 4-14 中,先调用 setVariable 和 setVariableLocal 方法,为 task 设置了 arg0和 arg1 两个参数,然后调用 getVariable 和 getVariableLocal 方法查找参数,其中使用 getVariable 方法查找两个参数, 使用 getVariableLocal 方法查找 days 参数,由于 days 参数是使用 setVariable 方法设置的,即 days 是流程的全局参数,因此使用 getVariableLocal 方法查找 days 时,将会返回 null。由此可见, getVariable 方法会查询任务参数 ( setVariableLocal 方法设置的参数〉和流程的全局参数(setVariable 方法设置的参数), 并且会优 先查询任务参数,而 getVariableLocal 方法只会查询任务参数(setVariableLocal 方法设置的参数)。 分别调用 setVariable 和 setVariableLocal 方法后,数据库的参数表的数据中使用 setVariable 方法设置参数时,并不会设置 TASK_ID 字段, 而使 用 setVariableLocal 方法设置参数时,则会设置相应的 TASK_ID 字段。在此需要注意的是, 如果希望有这样的效果,任务必须与流程进行绑定。
调用 setVariableLocal 方法设置的参数,仅仅在该任务存在的阶段可用, 一旦任务被删除或者完成,这些参数均会被删除,可到历史数据表中查询这些参数。运行代码清单 4-14,输出结果如下:
获取休假天数: 10
获取目的地: 欧洲
使用 getVariableLocal 方法获取天数: null

4.3.5、设置多个参数

如果一个任务需要设置多个参数,则可以定义一个序列化对象,将这些参数放到这个对象中,也可以使用 TaskService 的 setVariables 和 setVariablesLocal 方法,传入参数的 Map 集合, 参数 Map 的 key 为参数的名称, value 为参数值。 代码清单 4-15 使用 setVariables 方法设置了 多个参数。

public static void main(String[] args) {//获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();//保存第一个TaskTask task1 = taskService.newTask(UUID.randomUUID().toString());task1.setName("请假流程");taskService.saveTask(task1);//初始化参数Map<String,Object> vars = new HashMap<String, Object>();vars.put("days", 10);vars.put("target", "欧洲");taskService.setVariables(task1.getId(), vars);}

在代码清单 4-15 中,为参数的 Map 加入两个参数,分别为整型和字符串类型, 最后调用 set Variables 方法设置参数。实际上 setVariables 方法最终会遍历参数的 Map, 然后再逐一设置参数(重用 setVariable 逻辑)。 设置多个参数的 setVariablesLocal 方法,其逻辑与 setVariables 方法类似,遍历参数的 Map,再逐一设置参数(调用 setVariableLocal 方法),故 setVariablesLocal 方法的使用在此不再赘述。

4.3.6、数据对象

在 BPMN 文件中,可以使用 dataObject 元素来定义流程参数。流程启动后, 这些参数将会自动被设置到流程实例中,可以使用 RuntimeService、 TaskService 的方法来查询这些参数。 如代码清单 4-16 所示,在 BPMN中定义了 dataObject 元素。

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI"typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath"targetNamespace="http://www.activiti.org/test"><process id="vacationProcess" name="vacation"><!-- 定义了名称,默认值为 Crazyit --><dataObject id="personName" name="personName" itemSubjectRef="xsd:string"><extensionElements><activiti:value>Crazyit</activiti:value></extensionElements></dataObject><!-- 定义了年龄,默认值为20 --><dataObject id="personAge" name="personAge" itemSubjectRef="xsd:int"><extensionElements><activiti:value>20</activiti:value></extensionElements></dataObject><startEvent id="startevent1" name="Start"></startEvent><userTask id="usertask1" name="Write Vacation"></userTask><endEvent id="endevent1" name="End"></endEvent><sequenceFlow id="flow1" name="" sourceRef="startevent1"targetRef="usertask1"></sequenceFlow><sequenceFlow id="flow2" name="" sourceRef="usertask1"targetRef="endevent1"></sequenceFlow></process>
</definitions>

代码4-16中配置了两个参数,并未它们设定了默认值。代码4-17为获取参数的代码。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/dataObject.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 查询流程任务Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();// 获取全部参数Map<String, DataObject> objs = taskService.getDataObjects(task.getId());// 输出参数for(String key : objs.keySet()) {System.out.println(key + "---" + objs.get(key).getValue());}}

数据对象的配置将会在讲述 BPMN 的相关章节中讲述,获取参数的 API, 会在讲述 Runtime Service 时详细讲解。

4.4、任务附件管理

在实际生活中,许多流程都会带上一些任务相关的附件,例如报销流程有可能需要附上发票的单据,奖惩流程有可能会附上相关的说明文件。 为此, TaskService 中提供了用于附件管理的API,通过调用这些 API,可以对任务附件进行创建、删除和查询的操作。在 Activiti 中使用附件表 (ACT_HI_ATTACHMENT)保存任务附件数据,与其对应的实体类为 AttachmentEntityimpl。

4.4.1、Attachment对象

一个 Attachment 实例表示一条任务附件的数据, Attachment 接口提供了获取任务附件属性的各个方法,如何设置该对象的属性,完全由 TaskService 完成,使用者不需要关心设置的过程。该接口的实现类为 A忧achmentEntitylmpl,包括以下属性:
 id:附件表的主键,对应ID_字段。
revision:附件数据的版本,对应 REV_字段。
name:附件名称,由调用者提供,保存在 NAME_字段。
desciption:附件描述,由调用者提供,保存在 DESCRIPTION_字段。
type:附件类型,由调用者定义,保存在 TYPE_字段。
taskld: 该附件对应的任务 ID,对应 TASK_ID字段。
processlnstanceld: 流程实例 ID,对应 PROC_INST_ ID_字段。
url:附件的 URL,由调用者提供,对应 URL_字段。
contentld:附件内容的ID,如果调用者提供了输入流作为附件的内容,那么这些内容将会被保存到资源表 (ACT_GE_BYTEARRAY)中,该字段将为资源表的外键 ID, 对应 CONTENT_ID_ 字段。

4.4.2、创建任务附件

TaskService 中提供了两个创建任务附件的方法,这两个方法的描述如下。
>createAttachment(String attachmentType, String taskld, String processlnstanceld, String attachmentName, String attachmentDescription, String url):创建任务附件,attachmentType 为附件类型,由调用者定义; taskld 为任务 ID; processlnstanceld 为流程实例ID: attachmentName 为附件名称:attachmentDescription 为附件描述: url 为该附件的 URL 地址。
> createAttachment(String attachmentType, String taskld, String processlnstanceld, String attachmentName, String attachmentDescription, InputStream content): 该方法与前一个 createAttachment 方法一样,只是最后一个参数的类型为 InputStream。当调用该方法时, 会将最后的输出流参数转换为 byte 数组,并保存到资源表中,最后设置 CONTENT_ID_ 的字段值为资源表中的数据ID。 
在此需要注意的是,当调用第一个 createAttachment 方法时, 对于提供的 URL,Activiti 并不会解析它并生成 byte 数组,只是单纯地将该 URL 保存到 URL_字段中。在代码清单 4-18 中调用了这两个 createAttachment 方法。

public static void main(String[] args) throws Exception {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/vacation.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 查找任务Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();// 设置任务附件taskService.createAttachment("web url", task.getId(), pi.getId(), "163.com", "163 web page", "http://www.163.com");// 创建图片输入流InputStream is = new FileInputStream(new File("resource/artifact/result.png"));// 设置输入流为任务附件taskService.createAttachment("web url", task.getId(), pi.getId(), "163.com", "163 web page", is);}

代码清单 4-18 中别调用两个 createAttachment 方法创建任务附件。在第一 个 createAttachment 方法中,只提供了一个网站的 URL 地址让其创建附件数据:在第二个 createAttachment 方法中,以一个图片文件的输入流作为附件的数据。在日常的流程中,附件的形式是多种多样的,使用这两个 createAttachment 方法可以很灵活地设置不同类型的附件。运行代码 清单 4-18 后,可以查看资源表(ACT_GE_BYTEARRAY)与附件表(ACT_HI_ATTACHMENT) 中的数据。

4.4.3、附件查询

TaskService 中提供了四个查询附件的方法,这些方法包括根据任务 ID 查询附件集合、根据流程实例 ID 查询附件集合、根据附件 ID 查询附件数据和根据附件 ID 查询附件内容。以下 为这四个查询附件方法的描述:
> getProcesslnstanceAttachments(String processlnstanceld):根据流程实例 ID 查询该流程实例下全部的附件,返回 Attachment 集合。
> getTaskAttachments(String taskId):根据任务 ID 查询该任务下全部的附件,返回 Attachment 集合。
> getAttachment(String attachmentld):根据附件的 ID 查询附件数据,返回一个 Attachment 对象。
> getAttachmentContent(String attachmentld):根据附件的 ID 获取该附件的内容, 返回附件内容的输入流对象,如果调用的是第二个 createAttachment 方法(传入附件的 InputStream),那么调用该方法才会返回非空的输入流,否则将返回 null。
代码清单 4-19 示范了如何调用以上查询附件的方法。

public static void main(String[] args) throws Exception {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/vacation.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 查找任务Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();// 设置任务附件Attachment att1 = taskService.createAttachment("web url", task.getId(), pi.getId(), "Attachement1", "163 web page", "http://www.163.com");// 创建图片输入流InputStream is = new FileInputStream(new File("resource/artifact/result.png"));// 设置输入流为任务附件Attachment att2 = taskService.createAttachment("web url", task.getId(), pi.getId(), "Attachement2", "Image InputStream", is);// 根据流程实例ID查询附件List<Attachment> attas1 = taskService.getProcessInstanceAttachments(pi.getId());System.out.println("流程附件数量:" + attas1.size());// 根据任务ID查询附件List<Attachment> attas2 = taskService.getTaskAttachments(task.getId());System.out.println("任务附件数量:" + attas2.size());// 根据附件ID查询附件Attachment attResult = taskService.getAttachment(att1.getId());System.out.println("附件1名称:" + attResult.getName());// 根据附件ID查询附件内容     InputStream stream1 = taskService.getAttachmentContent(att1.getId());System.out.println("附件1的输入流:" + stream1);InputStream stream2 = taskService.getAttachmentContent(att2.getId());System.out.println("附件2的输入流:" + stream2);}

代码清单 4-19 中的调用了各个查询附件的方法 ,在最后调用了两次 getAttachmentContent 方法。调用第一个 getAttachmentContent 方法返回的结果为 null, 因为创建附件 1 ( att1 )的时候,并没有提供输入流对象,只提供了附件 URL 值;调用第二个 getAttachmentContent 方法返回的是一个输入流对象。运行代码清单 4-19, 输出结果如下: 
流程附件数盐: 2
任务附件数量: 2
附件 1 名称: Attachementl
附件 1 的输入流: null
附件 2 的输入流: j ava.io.ByteArrayI叩utStream@96212a

4.4.4、删除附件

如果需要删除附件数据,只需要调用 TaskService 的 deleteAttachment(String attachmentld) 方法即可。 如果在创建附件(调用 createAttachment 方法)时传入了输入流对象,那么将会在资源表中生成相应的资源数据,在调用 deleteAttachment 方法时, 并不会将相应的资源表的数据删除,只会删除附件表中的数据及相应的历史数据。 deleteAttachment 方法的使用较为简单, 在此不再赘述,读者可以参看样例代码:

taskService.deleteAttachment(att2.getId());

4.5、任务评论与事件记录

在日常的工作流程中,随着业务的进行,可能会夹杂着一些个人的流程意见,使用 Activiti, 可以将任务或者流程的评论保存到评论表(ACT_HI_COMMENT)中,接口为 Comment,实现类为 CommentEntityImpl。评论表会保存两种类型的数据:任务评论和部分事件记录。下面将介绍如何调用 TaskService 的方法管理这些任务评论数据与操作事件记录。

4.5.1、Comment对象

一个 Comment 实例表示评论表的一条数据, CornmentEntitylmpl 实际上实现了两个接口 : Event 和 Comment。如果该对象作为 Event 接口返回,则可以认为它返回的是事件记录的数据,如果该对象作为 Comment 返回,则其返回的是评论数据。在使用过程中,只可以得到 Comment 或者 Event 接口的实例, Comment 和 Event 接口中只定义了一系列的 getter 方法用于获取相关 信息,设置属性的方法,均被放到 TaskService 中实现。CommentEntitylmpl 主要包含以下属性:

> id:评论表的主键,对应 ID_字段
> type:该数据的类型,对应 TYPE_字段,该属性有两个值,分别为“event”和“comment’。当值为“event”时,表示该数据为事件的记录:当值为“comment”时,表示为任务或者流程的评论数据。
> userld:产生此数据用户的ID,对应 USER_ID_字段。
> time: 该数据的产生时间,对应 TIME_字段。
> taskld:该评论(或者事件记录)数据对应的任务 ID,对应 TASK_字段。
> processlnstanceld :该评论(或者事件记录)数据对应的流程实例ID, 对应 PROC_INST_ID_ 字段。
> action:该数据的操作标识,对应 ACTION 字段。
> message:该评论(或者事件记录)数据的信息,对应 MESSAGE_字段。
> fullMessage:该评论(或者事件记录)数据的信息,对应 FULL_MSG_字段。

4.5.2、新增任务评论

新增一个任务评论,可使用 TaskService 的 addComment方法,调用该方法需要传入taskld、 流程实例 ID 和信息参数。代码清单 4-20 示范了如何使用 addComment 方法。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/vacation.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 查找任务Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();// 添加任务评论taskService.addComment(task.getId(), pi.getId(), "this is comment message");// 查询评论List<Comment> comments = taskService.getTaskComments(task.getId());System.out.println("评论数量:" + comments.size());}

代码清单 4-20 使用 addComment 方法为其中的 Task 添加评论,执行完该代码后, 打开评论表,可以看到评论表中的评论数据,其中 TYPE 字段值为“comment”,表示该条数据为评论数据,并且 ACTION 字段值为“AddComment”。 ACTION_字段的值被定义在 Event 接口中,在调用特定方法时,会向评论表中写入数据,并且为 ACTION_字段设置相应的值。 ACTION_字段的各个值将在稍后讲解。

4.5.3、事件的记录

评论表中会保存方法的调用历史记录和任务(流程)评论数据,当调用不同方法时, ACTION_字段会被设置为不同的值,在代码清单 4-21 中即调用了 TaskService 的不同方法。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/vacation.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 查找任务Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();// 调用各个记录事件的方法 taskService.addComment(task.getId(), pi.getId(), "this is comment message");        taskService.addUserIdentityLink(task.getId(), "1", "user");taskService.deleteUserIdentityLink(task.getId(), "1", "user");taskService.addGroupIdentityLink(task.getId(), "1", "group");taskService.deleteGroupIdentityLink(task.getId(), "1", "group");Attachment atta = taskService.createAttachment("test", task.getId(), pi.getId(), "test", "test", "");taskService.deleteAttachment(atta.getId());// 查询Comment和EventList<Comment> comments = taskService.getTaskComments(task.getId());System.out.println("总共的评论数量:" + comments.size());List<Event> events = taskService.getTaskEvents(task.getId());System.out.println("总共的事件数量:" + events.size());}

运行代码清单 4-21 ,可以看到评论表中产生了若干条数据,其中 ACTION_字段会出现以下值。
>- AddUserLink:调用 TaskService 的 addUserldentityLink 方法时设置该值。
> DeleteUserLink:调用 TaskService 的 deleteUserldentityLink 方法时设置该值。
> AddGroup Link:调用 TaskService 的 addGroupldentityLink 方法时设置该值。
>- DeleteGroupLink:调用 TaskService 的 deleteGroupldentityLink 方法时设置该值。
>- AddComment:调用 TaskService 的 addComment 方法时设置该值。
>- AddA忧achment:调用 TaskService 的 createAttachment 方法时设置该值。
> DeleteAttachment:调用 TaskService 的 deleteA出chment 方法时设置该值。
根据产生的数据结果,可以得出结论,除了 addComment 方法外,其他的方法产生的数据中, TYPE_字段值均为“event”,表示这些数据为事件记录数据:而 addComment 方法产生的 数据中, TYPE_字段值为“comment”,表示这是一条任务 (流程) 的评论数据。在代码清单 4-21 的最后, 查询评论数据和事件记录数据。运行代码清单 4-21 , 输出结果如下:
总共的评论数量:1
 总共的事件数量: 7
 需要注意的是,历史数据配置(Activiti 配置文件中 processEngineConfiguration 配置中的 history 属性) 必须不能为 none,如果将 history 设置为 none, 那么将不会进行事件记录,并且 在调用 addComment 方法时,将会抛出以下异常:In order to use comments, history should be enabled。

4.5.4、数据查询

TaskService 中提供了以下几个查询评论表数据的方法。
>- getCornrnent(String commentld):根据评论数据 ID查询评论数据。
>- getTaskComments(String taskld): 根据任务 ID 查询相应的评论数据。
>- getTaskEvents(String taskld):根据任务ID 查询相应的事件记录。
>- getProcesslnstanceComments(String processlnstanceld):根据流程实例 ID 查询相应的评 论(事件)数据。
其中, getTaskEvents方法返回 Event 的实例集合,而 getTaskComments和 getProcesslnstanceComments 方法返回 Comment 的实例集合。在代码清单 4-22 中使用 了以上三个查询数据方法。

public static void main(String[] args) {// 获取流程引擎实例ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();// 获取任务服务组件TaskService taskService = engine.getTaskService();// 获取运行服务组件RuntimeService runtimeService = engine.getRuntimeService();// 流程存储服务组件RepositoryService repositoryService = engine.getRepositoryService();// 部署流程描述文件Deployment dep = repositoryService.createDeployment().addClasspathResource("bpmn/vacation.bpmn").deploy();// 查找流程定义ProcessDefinition pd = repositoryService.createProcessDefinitionQuery().deploymentId(dep.getId()).singleResult();// 启动流程ProcessInstance pi = runtimeService.startProcessInstanceById(pd.getId());// 查找任务Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();// 调用各个记录事件的方法 taskService.addComment(task.getId(), pi.getId(), "this is comment message");        taskService.addUserIdentityLink(task.getId(), "1", "user");taskService.deleteUserIdentityLink(task.getId(), "1", "user");taskService.addGroupIdentityLink(task.getId(), "1", "group");taskService.deleteGroupIdentityLink(task.getId(), "1", "group");Attachment atta = taskService.createAttachment("test", task.getId(), pi.getId(), "test", "test", "");taskService.deleteAttachment(atta.getId());// 查询事件与评论List<Comment> commonts1 = taskService.getProcessInstanceComments(pi.getId());System.out.println("流程评论(事件)数量:" + commonts1.size());commonts1 = taskService.getTaskComments(task.getId());System.out.println("任务评论数量:" + commonts1.size());List<Event> events = taskService.getTaskEvents(task.getId());System.out.println("事件数量:" + events.size());}

代码清单 4-22 中的粗体字代码分别调用三个查询 ACT_HI_COMMENT 表数据的方法,在调用 getProcesslnstanceComments 方法时,不会判断数据类型是否为“comment”,只要流程实例 id 匹配,则会将数据查询出来。运行代码清单 4-22,输出结果如下:
流程评论(事件)数量: 3 任务评论数量: 1 事件数量: 7

4.6、任务声明与完成

调用 Taskservice 的 claim 方法可以将任务分配到用户下,即设置任务表的 ASSIGNEE_字段值为用户的ID, 该效果与使用 setAssignee 方法的效果类似,但是不同的是, 一旦调用了 claim 方法声明任务的代理人,如果再次调用该方法将同一个任务分配到另外的用户下,则会抛出异 常。 代码清单 4-23 为一个使用 claim 方法的示例。

工作流学习2(书本)相关推荐

  1. Activiti工作流学习笔记01

    Activiti6工作流学习笔记01 activiti工作流目前官方最新版本是7.x,但....版本不重要了.这篇笔记只是我学习activiti6过程中的自我总结.如果笔记上有错误的话,欢迎赐教,谢谢 ...

  2. Activiti工作流学习之流程图应用详解

    Activiti工作流学习之流程图应用详解 1.目的 了解Activiti工作流是怎样应用流程图的. 2.环境准备 2.1.相关软件及版本 jdk版本:Jdk1.7及以上 IDE:eclipse 数据 ...

  3. 工作流学习——Activiti整体认识二步曲

    一.前言 在上一篇文章中我们将工作流的相关概念.activiti的前世今生.activiti与jbpm的比较进行学习,这篇文章我们正式进行activiti的学习,activiti的整个学习我们主要通过 ...

  4. Git工作流学习笔记

    Git工作流指南 分布式版本控制系统 观看笔记:https://www.bilibili.com/video/BV1dW411U7ER?p=1 老师笔记: http://www.funtl.com/z ...

  5. Python爬虫学习-新华书店书本数据定向爬虫(实例)

    在学习了半个月python基础知识以及相关的爬虫基础技术后,尝试学以致用,本次决定使用requests.BeautifulSoup来对新华书店(https://www.xhsd.com)小说类书本商品 ...

  6. Shark工作流学习日记

    前天下了北京今年的第一场雪,细细的雪粒,砸在路面上,感觉很舒服,所有的烦恼就这样被一扫而光. 今天天气也非常冷,属于干冷的那种,凉簌簌的感觉,让你觉得触摸到什么都会不痛快,而我还在这样的温度.这样的时 ...

  7. 工作流学习——Activiti流程实例、任务管理四步曲 (zhuan)

    http://blog.csdn.net/zwk626542417/article/details/46646565 ***************************************** ...

  8. Activiti工作流学习篇(一)

    一.Activiti 的概念       工作流(Workflow),指"业务过程的部分或整体在计算机应用环境下的自动化".是对工作流程及其各操作步骤之间业务规则的抽象.概括描述. ...

  9. activiti工作流学习(二)

    流程连线 流程开始→某个学生请假(一般情况/特殊情况)→班长审批通过→(进行判断,如果是一般情况直接结束;如果是特殊情况流程指向教师审批,教师审批结束后)→结束 任务设定 学生请假,执行人设置为 ${ ...

最新文章

  1. QIIME 2教程. 25可用和开发中插件AvailableFuturePlugins(2020.11)
  2. 8个独立按键控制LED
  3. Acwing第 7 场周赛【未完结】
  4. JVM_06 垃圾收集器[ 三 ]
  5. debian9 没有ipv4
  6. 机器学习与差分隐私(认证鲁棒性和隐私保护)
  7. wns服务器没有响应,如何使用 Windows 推送通知服务 (WNS) 进行验证(Windows 运行时应用)...
  8. windows11虚拟机安装失败解决办法
  9. am相干解调matlab文档,AM调制与相干解调
  10. C#方法名前的方括号作用
  11. 【枚举·习题】拉灯游戏or费解的开关
  12. 【论文阅读】A Survey of Incentive Mechanism Design for Federated Learning 联邦学习激励机制设计综述
  13. ftp木马病毒photo.scr,Video.scr,AV.scr文件处理方法(windows服务器)
  14. tplinkwr710n改无线打印服务器,TP-Link TL-WR710N V1无线路由器AP模式怎么设置
  15. 大毕业什么都没学到 就是收藏了超级实用的130个网站!!!
  16. LTspice基础教程-010.波形查看器的使用
  17. BUPT-CSAPP 期末复习书后参考题节选及评注
  18. blob导出的excel文件打不开,responseType: “blob“ 但 response回应类型不是blob类型,response.data是乱码。
  19. ICG试剂 ICG-PEG-NHS,ICG-PEG-SE光谱图分享
  20. pta c语言段错误,PTA常见错误

热门文章

  1. NLP学习笔记14-语言模型(下)
  2. android x86 uefi 安装教程,如何在Hyper-V上安装android-x86系统
  3. 我想谈一谈外包,请不要“妖魔化”它。
  4. npm run serve stage1@0.1.0 serve vue-cli-service serve node:internal/modules/cjs/loader:936
  5. 白话机器学习-Encoder-Decoder框架
  6. php guzzlehttp,使用Guzzle执行HTTP请求
  7. 你不知道的VLC播放器常用痛点功能——快进、快捷键、剪切视频、旋转画面、视频提取声音等
  8. 计算机与测控技术专业就业方向,测控技术与仪器专业就业前景与方向(五篇)
  9. jpress连接数据库mysql_win10+java+mysql+tomcat+jpress环境搭建与部署
  10. 完整部署uniswap 合约、前端教程(可部署uniswap到bsc、heco)