引用jar包
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>5.22.0</version>
</dependency>
安装流程设计器(eclipse插件)
在线安装:
Name: Activiti BPMN 2.0 designer
Location: http://activiti.org/designer/update/
离线安装包:https://github.com/Activiti/Activiti-Designer/releases
在安装离线安装包时,需要下载4个eclipse的插件,否则activiti插件包安装时会报错
org.eclipse.emf.transaction-1.8.0.201405281451.jar
org.eclipse.emf.validation.ui-1.7.0.201405281429.jar
org.eclipse.emf.validation-1.8.0.201405281429.jar
org.eclipse.emf.workspace-1.5.1.201405281451.jar
以上四个jar可以在maven仓库https://dist.wso2.org/maven2/处下载
如果在安装离线包的时候可以互联网,建议先断开;否则会出现联网更新,导致安装一直没有进度。
生成流程图片(非必须)
打开菜单Windows->Preferences->Activiti->Save下流程流程图片的生成方式:
虽然流程引擎在单独部署bpmn文件时会自动生成图片,但在实际开发过程中,自动生成的图片会导致和BPMN中的坐标有出入,在实际项目中展示流程当前位置图会有问题。所在完成以上配置后,会由我们自己来管理流程图片。在发布流程时把流程规则文件和流程图片一起上传就行了。
初始化数据库
public static void main(String[] args) {
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
//连接数据库的配置
processEngineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver");
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8");
processEngineConfiguration.setJdbcUsername("root");
processEngineConfiguration.setJdbcPassword("root");
/**
* 数据库初始化策略
public static final String DB_SCHEMA_UPDATE_FALSE = "false";不能自动创建表,需要表存在
public static final String DB_SCHEMA_UPDATE_CREATE_DROP = "create-drop";先删除表再创建表
public static final String DB_SCHEMA_UPDATE_TRUE = "true";如果表不存在,自动创建表
*/
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
//工作流的核心对象,ProcessEnginee对象
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
System.out.println("processEngine:"+processEngine);
}
如果程序正常执行,mysql会自动建库,然后创建23张表
根据xml文件初始化数据库
创建activiti.cfg.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- dbcp数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!-- 使用默认的MyBatis连接池配置数据源,还有一些连接池参数就不展示了,实际开发中都不会使用用默认 -->
<property name="jdbcDriver" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8"></property>
<property name="jdbcUsername" value="root"></property>
<property name="jdbcPassword" value="root"></property>
<!-- 使用其他数据源 -->
<!-- <property name="dataSource" ref="dataSource"></property> -->
<!--
databaseSchemaUpdate=true 如果表不存在,自动创建表
databaseSchemaUpdate=false 默认值,不自动创建表,需要表存在
databaseSchemaUpdate=create-drop 如果表不存在,自动创建表
-->
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
public static void main(String[] args) {
//指定路径加载,这里的路是指classpath下的路径
ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("config/activiti.cfg.xml")
.buildProcessEngine();
//默认路径加载,默认路径为:classpath:activiti.cfg.xml
//ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println("processEngine:" + processEngine);
}
创建一个流程,这里以userTask流程为例
1. 在src/main/resources/diagrams路径下创建helloworld.bpmn
2. 编写入门示例代码
第一步:获取流程引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
第二步:部署流程定义
//部署流程方式1:从classpath下加载.bpmn和.png
Deployment deployment = processEngine.getRepositoryService()//与流程定义和部署对象相关的Service
.createDeployment()//创建一个部署对象
.name("helloworld入门程序")//添加部署的名称
.addClasspathResource("diagrams/helloworld.bpmn")//从classpath的资源中加载,一次只能加载一个文件
//.addClasspathResource("diagrams/helloworld.png")//从classpath的资源中加载,一次只能加载一个文件,非必须,如果没有创建png,默认会在数据库中自动创建
.deploy();//完成部署
System.out.println("部署ID:"+deployment.getId());
System.out.println("部署名称:"+deployment.getName());
//部署流程方式2:从zip包加载
//zip包只能包含.bpmn和.png文件
InputStream in = this.getClass().getClassLoader().getResourceAsStream("diagrams/test.zip"); //获取zip包的输入流对象
ZipInputStream zipInputStream = new ZipInputStream(in);
Deployment deployment = processEngine.getRepositoryService()
.createDeployment()
.name("helloworld入门程序")
.addZipInputStream(zipInputStream) //指定zip格式的文件完成部署
.deploy();
//部署流程方式3:从InputStream加载
InputStream inputStreambpmn = this.getClass().getResourceAsStream("/diagrams/helloworld.bpmn"); //获取.bpmn的输入流
InputStream inputStreampng = this.getClass().getResourceAsStream("/diagrams/helloworld.png"); //获取.png的输入流
Deployment deployment = processEngine.getRepositoryService()
.createDeployment()
.name("helloworld入门程序")
.addInputStream("helloworld-inputStream.bpmn", inputStreambpmn) //添加资源文件,名称可以随便取
.addInputStream("helloworld-inputStream.png", inputStreampng) //添加资源文件,名称可以随便取
.deploy();//完成部署
综上所述:流程图画好之后,需要先进行部署才能使用;但是需要注意的是,如果流程图没有发生变化,最好不要每次启动程序的时候就去调用部署代码,这样会造成流程部署表和流程定义表的数据一直在发生变化,实际上流程本身并没有变化。
第三步:启动流程实例
//启动流程实例,也就是开始执行流程了,从开始节点到结束节点,依次执行;
//当一个流程启动后,流程实例会贯穿整个流程,流程实例id是不会发生变化的
String processDefinitionKey = "helloworld"; //key对应helloworld.bpmn文件中id的属性值,使用key值启动,当流程定义表中存在多个相同的key值时,默认是按照最新版本的流程定义启动
ProcessInstance pi = processEngine.getRuntimeService()//与正在执行的流程实例和执行对象相关的Service
.startProcessInstanceByKey(processDefinitionKey);//使用流程定义的key启动流程实例,
System.out.println("流程实例ID:"+pi.getId());//流程实例ID
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());//流程定义ID
综上所述:带ID的字段基本都是主键,各表之间相互引用关联。
第四步:查询任务
//当前节点为提交申请,办理人为张三
String assignee = "张三"; //对应helloworld.bpmn-properties-mianconfig-assignee
List<Task> list = processEngine.getTaskService()//与正在执行的任务管理相关的Service
.createTaskQuery()//创建任务查询对象
.taskAssignee(assignee)//指定任务办理人
.list();
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
}
}
任务ID:2504
任务名称:提交申请
任务的创建时间:Sun Jan 05 20:55:31 CST 2020
任务的办理人:张三
流程实例ID:2501
执行对象ID:2501
流程定义ID:helloworld:1:4
第五步:完成任务
//模拟张三完成了任务,进入下一个节点
String taskId = "2504"; //act_ru_task表的id
processEngine.getTaskService().complete(taskId);
System.out.println("完成任务:任务ID:"+taskId);
第六步:依次查询任务和完成任务
//模拟最后一个节点王五完成任务
String taskId = "7502"; //act_ru_task表的id
processEngine.getTaskService().complete(taskId);
System.out.println("完成任务:任务ID:"+taskId);
完整的代码如下:
package cn.fg.activiti.general;
import java.util.List;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;
public class Test03 {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //默认加载类路径下的activiti.cfg.xml
/**第一步:部署流程定义*/
@Test
public void deploymentProcessDefinition(){
Deployment deployment = processEngine.getRepositoryService()//与流程定义和部署对象相关的Service
.createDeployment()//创建一个部署对象
.name("helloworld入门程序")//添加部署的名称
.addClasspathResource("diagrams/helloworld.bpmn")//从classpath的资源中加载,一次只能加载一个文件
//.addClasspathResource("diagrams/helloworld.png")//从classpath的资源中加载,一次只能加载一个文件
.deploy();//完成部署
System.out.println("部署ID:"+deployment.getId());//1
System.out.println("部署名称:"+deployment.getName());//helloworld入门程序
//涉及数据表
//act_re_deployment 部署对象表
//act_re_procdef 流程定义表
//act_ge_bytearray 资源文件表
//act_ge_property 属性表,主键生成策略
}
/**第二步:启动流程实例,流程开始执行,进入提交申请节点 */
@Test
public void startProcessInstance(){
//流程定义表中的key对用流程图的id
String processDefinitionKey = "helloworld";
ProcessInstance pi = processEngine.getRuntimeService()//与正在执行的流程实例和执行对象相关的Service
.startProcessInstanceByKey(processDefinitionKey);//使用流程定义的key启动流程实例,key对应helloworld.bpmn文件中id的属性值,使用key值启动,默认是按照最新版本的流程定义启动
System.out.println("流程实例ID:"+pi.getId());//流程实例ID 101
//流程定义ID helloworld:1:4 helloworld表示.bpmn文件的id值(act_re_procdef表的key值),1表示当前版本,4表示随机生成的,更改流程图后版本号会变化,每次启动流程实例都是以最新版本号启动的
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());
//启动后进入第二个节点
//涉及数据表
//act_ru_execution 增在执行的执行对象
//act_hi_procinst 流程实例历史表
//act_ru_identitylink
//act_ru_task 正在执行的任务表(只有流程节点是userTask时此表才会有数据)
//act_hi_taskinst 任务历史表(只有流程节点是userTask时此表才会有数据
//act_hi_actinst 所有活动节点的历史表
}
/**第三步:查询办理人张三的任务*/
@Test
public void findMyPersonalTask(){
String assignee = "张三"; //对应helloworld.bpmn-properties-mianconfig-assignee
List<Task> list = processEngine.getTaskService()//与正在执行的任务管理相关的Service
.createTaskQuery()//创建任务查询对象
.taskAssignee(assignee)//指定个人任务查询,指定办理人
.list();
if(list!=null && list.size()>0){
for(Task task:list){
System.out.println("任务ID:"+task.getId());
System.out.println("任务名称:"+task.getName());
System.out.println("任务的创建时间:"+task.getCreateTime());
System.out.println("任务的办理人:"+task.getAssignee());
System.out.println("流程实例ID:"+task.getProcessInstanceId());
System.out.println("执行对象ID:"+task.getExecutionId());
System.out.println("流程定义ID:"+task.getProcessDefinitionId());
System.out.println("########################################################");
}
}
}
/**第四步:模拟张三完成任务,进入下一个节点*/
@Test
public void completeMyPersonalTask(){
//任务ID,步骤3输出的任务id
String taskId = "12502"; //act_ru_task表的id
processEngine.getTaskService()//与正在执行的任务管理相关的Service
.complete(taskId); //act_ru_task表id为5004的记录消失,进入下一个节点
System.out.println("完成任务:任务ID:"+taskId);
//涉及数据表
//act_hi_taskinst 任务历史
}
/**第五步:查询办理人李四的任务*/
@Test
public void findMyPersonalTask(){
//同步骤三
String assignee = "李四";
}
/**第六步:模拟李四完成任务,进入下一个节点*/
@Test
public void completeMyPersonalTask(){
//同步骤四
String taskId = "";
}
.......不再写了,就是要查任务,完成任务。
}
数据库表说明
Activiti的数据库名称都以ACT_开头,第二部分的两个字母表示用例标识。 用例标识和服务API对应(例如ACT_RE_*表和RuntimeService的API对应,即RuntimeService类可以操作ACT_RE_*表的CRUD)
ACT_RE_ *:RE代表repository。具有此前缀的表包含静态信息,例如流程定义和流程资源(图像,规则等)。
ACT_RU_ *:RU代表runtime。这些是运行时表,其中包含流程实例,用户任务,变量,作业等的运行时数据。Activiti仅在流程实例执行期间存储运行时数据,并在流程实例结束时删除记录。这样可以使运行时表较小而又快速。
ACT_ID_ *:ID代表identity。这些表包含身份信息,例如用户,组等。
ACT_HI_ *:HI代表history。这些表包含历史数据,例如过去的流程实例,变量,任务等。
ACT_GE_ *:general通用数据,用于不同的场景中。
具体的表结构查询:https://www.devdoc.cn/activiti-table-summary.html
核心API
1. ProcessEngine
在Activiti中最核心的类,其他的类都是由他而来,创建该类的方式
//方式一:使用编码的方式创建
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration();
processEngineConfiguration.setJdbcDriver("com.mysql.jdbc.Driver");
processEngineConfiguration.setJdbcUrl("jdbc:mysql://localhost:3306/activiti?useUnicode=true&characterEncoding=utf8");
processEngineConfiguration.setJdbcUsername("root");
processEngineConfiguration.setJdbcPassword("root");
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
//方式二:指定xml文件路径创建,该路径是classpath下的路径
ProcessEngine processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("config/activiti.cfg.xml").buildProcessEngine();
//方式三:使用默认流程引擎,默认使用classpath下的activiti.cfg.xml文件创建
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
2. RepositoryService
是Activiti的仓库服务类。所谓的仓库指流程定义文档的两个文件:bpmn文件和流程图片。
RepositoryService repositoryService = processEngine.getRepositoryService();
3. RuntimeService
是activiti的流程执行服务类。可以从这个服务类中获取很多关于流程执行相关的信息。
RuntimeService runtimeService = processEngine.getRuntimeService();
4. TaskService
是activiti的任务服务类。可以从这个类中获取任务的信息。
TaskService taskService = processEngine.getTaskService();
5. HistoryService
是activiti的查询历史信息的类。在一个流程执行完成后,这个对象为我们提供查询历史信息。
HistoryService historyService = processEngine.getHistoryService();
6. ProcessDefinition
流程定义类。可以从这里获得资源文件等。
7. ProcessInstance
代表流程定义的执行实例。如范冰冰请了一天的假,她就必须发出一个流程实例的申请。一个流程实例包括了所有的运行节点。我们可以利用这个对象来了解当前流程实例的进度等信息。流程实例就表示一个流程从开始到结束的最大的流程分支,即一个流程中流程实例只有一个。
8. Execution
Activiti用这个对象去描述流程执行的每一个节点。一个流程中,执行对象可以存在多个,但是流程实例只能有一个。在没有并行任务的情况下,Execution就是ProcessInstance。
流程定义的CRUD
1. 查询流程定义
List<ProcessDefinition> list = processEngine.getRepositoryService()//与流程定义和部署对象相关的Service
.createProcessDefinitionQuery()//创建一个流程定义的查询
/**指定查询条件,where条件*/
//.deploymentId(deploymentId)//使用部署对象ID查询
//.processDefinitionId(processDefinitionId)//使用流程定义ID查询
//.processDefinitionKey(processDefinitionKey)//使用流程定义的key查询
//.processDefinitionNameLike(processDefinitionNameLike)//使用流程定义的名称模糊查询
/**排序*/
.orderByProcessDefinitionVersion().asc()//按照版本的升序排列
//.orderByProcessDefinitionName().desc()//按照流程定义的名称降序排列
/**返回的结果集*/
.list();//返回一个集合列表,封装流程定义
//.singleResult();//返回惟一结果集
//.count();//返回结果集数量
//.listPage(firstResult, maxResults);//分页查询
if(list!=null && list.size()>0){
for(ProcessDefinition pd:list){
System.out.println("流程定义ID:"+pd.getId());//流程定义的key+版本+随机生成数
System.out.println("流程定义的名称:"+pd.getName());//对应helloworld.bpmn文件中的name属性值
System.out.println("流程定义的key:"+pd.getKey());//对应helloworld.bpmn文件中的id属性值
System.out.println("流程定义的版本:"+pd.getVersion());//当流程定义的key值相同的相同下,版本升级,默认1
System.out.println("资源名称bpmn文件:"+pd.getResourceName());
System.out.println("资源名称png文件:"+pd.getDiagramResourceName());
System.out.println("部署对象ID:"+pd.getDeploymentId());
System.out.println("#########################################################");
}
}
//查询其他对象也是一样的方式,这里就不再介绍了
2. 删除流程定义
//使用部署ID,完成删除
String deploymentId = "601";
//不带级联的删除,只能删除没有启动的流程,如果流程启动,就会抛出异常
processEngine.getRepositoryService().deleteDeployment(deploymentId);
processEngine.getRepositoryService().createDeploymentQuery().
//级联删除,不管流程是否启动,都能可以删除
processEngine.getRepositoryService().deleteDeployment(deploymentId, true);
System.out.println("删除成功!");
3. 查看流程图
//部署ID
String deploymentId = "801";
//获取图片资源名称
List<String> list = processEngine.getRepositoryService().getDeploymentResourceNames(deploymentId);
//定义图片资源的名称
String resourceName = "";
if(list!=null && list.size()>0){
for(String name:list){
if(name.indexOf(".png")>=0){
resourceName = name;
}
}
}
//获取图片的输入流
InputStream in = processEngine.getRepositoryService().getResourceAsStream(deploymentId, resourceName);
//将图片生成到D盘的目录下
File file = new File("D:/"+resourceName);
//将输入流的图片写到D盘下
org.apache.commons.io.FileUtils.copyInputStreamToFile(in, file);
4. 查询最新版本的流程定义
List<ProcessDefinition> list = processEngine.getRepositoryService()
.createProcessDefinitionQuery()//
.orderByProcessDefinitionVersion().asc()//使用流程定义的版本升序排列
.list();
/**
Map<String,ProcessDefinition>
map集合的key:流程定义的key
map集合的value:流程定义的对象
map集合的特点:当map集合key值相同的情况下,后一次的值将替换前一次的值
*/
Map<String, ProcessDefinition> map = new LinkedHashMap<String, ProcessDefinition>();
if(list!=null && list.size()>0){
for(ProcessDefinition pd:list){
map.put(pd.getKey(), pd);
}
}
List<ProcessDefinition> pdList = new ArrayList<ProcessDefinition>(map.values());
if(pdList!=null && pdList.size()>0){
for(ProcessDefinition pd:pdList){
System.out.println("流程定义ID:"+pd.getId());//流程定义的key+版本+随机生成数
System.out.println("流程定义的名称:"+pd.getName());//对应helloworld.bpmn文件中的name属性值
System.out.println("流程定义的key:"+pd.getKey());//对应helloworld.bpmn文件中的id属性值
System.out.println("流程定义的版本:"+pd.getVersion());//当流程定义的key值相同的相同下,版本升级,默认1
System.out.println("资源名称bpmn文件:"+pd.getResourceName());
System.out.println("资源名称png文件:"+pd.getDiagramResourceName());
System.out.println("部署对象ID:"+pd.getDeploymentId());
System.out.println("#########################################################");
}
}
5. 删除流程定义(删除key相同的所有不同版本的流程定义)
//流程定义的key
String processDefinitionKey = "helloworld";
//先使用流程定义的key查询流程定义,查询出所有的版本
List<ProcessDefinition> list = processEngine.getRepositoryService()//
.createProcessDefinitionQuery()//
.processDefinitionKey(processDefinitionKey)//使用流程定义的key查询
.list();
//遍历,获取每个流程定义的部署ID
if(list!=null && list.size()>0){
for(ProcessDefinition pd:list){
//获取部署ID
String deploymentId = pd.getDeploymentId();
processEngine.getRepositoryService().deleteDeployment(deploymentId, true);
}
}
6. 对于修改操作是有限制的,一般使用set方法,能set的就可以修改
processEngine.getRepositoryService().setProcessDefinitionCategory(processDefinitionId, category);
7. 判断流程是否结束
String processInstanceId = "2504";
/**判断流程是否结束,查询正在执行的执行对象表*/
ProcessInstance rpi = processEngine.getRuntimeService()//
.createProcessInstanceQuery()//创建流程实例查询对象
.processInstanceId(processInstanceId)
.singleResult();
//说明流程实例结束了
if(rpi==null){
/**查询历史,获取流程的相关信息*/
HistoricProcessInstance hpi = processEngine.getHistoryService()//
.createHistoricProcessInstanceQuery()//
.processInstanceId(processInstanceId)//使用流程实例ID查询
.singleResult();
System.out.println(hpi.getId()+" "+hpi.getStartTime()+" "+hpi.getEndTime()+" "+hpi.getDurationInMillis());
}
查询历史
1. 查询历史流程实例
String processInstanceId = "2101";
HistoricProcessInstance hpi = processEngine.getHistoryService()//与历史数据(历史表)相关的Service
.createHistoricProcessInstanceQuery()//创建历史流程实例查询
.processInstanceId(processInstanceId)//使用流程实例ID查询
.orderByProcessInstanceStartTime().asc()
.singleResult();
System.out.println(hpi.getId()+" "+hpi.getProcessDefinitionId()+" "+hpi.getStartTime()+" "+hpi.getEndTime()+" "+hpi.getDurationInMillis());
2. 查询历史活动节点(act_hi_actinst)
String processInstanceId = "2101";
List<HistoricActivityInstance> list = processEngine.getHistoryService()//
.createHistoricActivityInstanceQuery()//创建历史活动实例的查询
.processInstanceId(processInstanceId)//
.orderByHistoricActivityInstanceStartTime().asc()//
.list();
if(list!=null && list.size()>0){
for(HistoricActivityInstance hai:list){
System.out.println(hai.getId()+" "+hai.getProcessInstanceId()+" "+hai.getActivityType()+" "+hai.getStartTime()+" "+hai.getEndTime()+" "+hai.getDurationInMillis());
System.out.println("#####################");
}
}
3. 查询历史任务
String processInstanceId = "2101";
List<HistoricTaskInstance> list = processEngine.getHistoryService()//与历史数据(历史表)相关的Service
.createHistoricTaskInstanceQuery()//创建历史任务实例查询
.processInstanceId(processInstanceId)//
.orderByHistoricTaskInstanceStartTime().asc()
.list();
if(list!=null && list.size()>0){
for(HistoricTaskInstance hti:list){
System.out.println(hti.getId()+" "+hti.getName()+" "+hti.getProcessInstanceId()+" "+hti.getStartTime()+" "+hti.getEndTime()+" "+hti.getDurationInMillis());
System.out.println("################################");
}
}