@[toc]上一篇宋哥跟大家聊了聊Flowable中的流程部署。今天我们继续讲流程实例。部署后的流程,这个不能直接运行。例如,我们部署了一个请假流程。现在张三要请假,他需要启动一个请假流程。请假流程是一个流程实例,我们一开始部署的请假流程类似于一个模板。基于这个模板,我们可以打开很多具体的流程实例。从这个角度来说,上一篇我们定义的ProcessDefinition类似于一个Java类,而今天要介绍的ProcessInstance相当于一个Java对象。1.明确三个概念首先我们要明确三个概念:流程定义ProcessDefinition流程实例ProcessInstance执行实例Execution流程定义流程定义ProcessDefinition这个说起来容易,其实就是我们上一篇文章给大家介绍的。将流程XML文件部署到flowable中是一个已定义的流程。基于这个定义好的流程,我们可以打开很多流程实例。流程实例ProcessInstance是由流程定义启动的流程。它代表了一个流程从开始到结束的最大流程分支。在一个流程中,只有一个流程实例。流程实例和流程定义的关系类似于Java对象和Java类的关系。执行实例执行有点难理解。首先,从类关系来看,ProcessInstance是Execution的子类。流程实例通常是执行实例的根节点,即在一个流程中,出口和入口可以看作是一个流程实例的节点,中间流程就是执行实例。如果流程本身是一条线,那么流程实例和执行实例基本是一样的,但是如果流程包含多条线,比如下图:这个图中有并行网关,执行并行任务时,每个并行任务都是一个Executingexamples,方便大家理解。得出的结论是,在一个流程实例中,除了开始和结束之外,其他所有的实例都是执行实例。即使流程只有一行,中间的都是执行实例,但是此时的执行实例等于流程实例。好吧,我们先搞清楚三个基本概念。2.启动流程的五种方式我们部署好流程之后,接下来就是启动流程。我们有五种不同的方式来启动流程。通过流程定义的id启动就是通过流程定义的id来启动一个流程。对应的方法名是RuntimeService#startProcessInstanceById。这个方法有几个重载方法。不同的重载方法只是传递不同的参数。其他一切基本相同。您还可以通过流程定义的键来启动流程。根据上一篇文章的介绍,大家知道这个流程定义的key其实就是流程XML文件中的id。对应的方法名是RuntimeService#startProcessInstanceByKey。有这样一种情况,通过进程的key+tenantId启动。比如我有两个子系统A和B,在A和B中分别定义了一个请假流程,现在我要启动一个流程,怎么知道是启动A的请假流程还是启动B的请假流程呢?此时我们可以通过租户ID来区分,即tenantId,所以在进程启动的时候还有一个方法RuntimeService#startProcessInstanceByKeyAndTenantId。通过进程的消息来启动进程通过消息来启动进程,对应的方法是RuntimeService#startProcessInstanceByMessage。通过进程的message+tenanId启动一个进程。通过消息+租户ID启动一个流程。对应的方法是RuntimeService#startProcessInstanceByMessageAndTenantId。3、简单实践首先我们画一个简单的流程图,然后按照上一篇介绍的方法进行部署。流程图如下:流程XML文件如下:initiator="INITIATOR">让我告诉你这个XML文件。在启动节点上,我设置了flowable:initiator="INITIATOR",相当于把流程启动器的变量定义为INITIATOR。这个变量名是自定义的。定义好之后,以后我可以在其他节点使用这个改了一个很简单的流程,其中:提交请假申请,由流程的发起者完成。主管是张三。经理是lisi。嗯,先按照我们上篇介绍的方式部署流程。接下来我们要启动进程,假设我们使用进程定义的key来启动一个进程实例:@SpringBootTestpublicclassRuTest{@AutowiredRuntimeServiceruntimeService;privatestaticfinalLoggerlogger=LoggerFactory.getLogger(RuTest.class);@Testvoidtest01(){Authentication.setAuthenticatedUserId("wangwu");ProcessInstancepi=runtimeService.startProcessInstanceByKey("离开");logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId());}}启动代码其实很简单。当流程启动成功后,流程中的每一步都会记录在ACT_RU_EXECUTION表中。同时,如果这个节点是用户任务节点(UserTask),也会在ACT_RU_TASK表中添加一条记录。Authentication.setAuthenticatedUserId("网屋");指示设置过程的发起者。另一种设置进程启动器的方法如下:@AutowiredIdentityServiceidentityService;@Testvoidtest01(){identityService.setAuthenticatedUserId("wangwu");ProcessInstancepi=runtimeService.startProcessInstanceByKey("离开");logger.info("id:{},activityId:{}",pi.getId(),pi.getActivityId());}对于我们上面的流程,启动后会进入提交请假的节点,所以有是两个节点,那么ACT_RU_EXECUTION表中应该有两条记录,如下图:我们看一下ACT_RU_TASK表的内容:可以看到表中有一条记录,其实就是提交请假申请的节点。现在流程在这一步就被Stopped了,需要用户手动操作才能继续往下走。从这两张表中,我们可以大致看出EXECUTION和ProcessInstance的关系。ACT_RU_EXECUTION表中的每条记录都是一个EXECUTION,多个EXECUTION对应同一个PROC_INST_ID_,ACT_RU_TASK表中的每条Task记录也对应一个EXECUTION。现在我们先查询wangwu需要完成的Task(wangwu是进程的发起者):@AutowiredTaskServicetaskService;@Testvoidtest02(){Listlist=taskService.createTaskQuery().taskAssignee("wangwu").列表();for(Tasktask:list){logger.info("id:{};name:{};taskDefinitionKey:{}",task.getId(),task.getName(),task.getTaskDefinitionKey());}}根据前面的介绍,我们知道这个查询肯定是在ACT_RU_TASK表中查询的。我们看一下执行的SQL:可以看到,任务是根据ASSIGNEE_字段进行查询的。查询任务后,完成任务:@Testvoidtest03(){Listlist=taskService.createTaskQuery().taskAssignee("wangwu").list();对于(任务任务:列表){taskService。完成(任务。getId());}}意思是查询wangwu的任务,然后完成。该方法执行后,会先在ACT_RU_TASK表中插入一个需要张三完成的新Task,然后更新ACT_RU_EXECUTION表中对应的任务。执行实例信息,最后从ACT_RU_TASK表中删除需要wangwu完成的记录。这些操作在同一个事务中完成。那么,现在我们执行test02的查询方法,会发现找不到,因为没有wangwu需要完成的任务。接下来,我们应该查询一下张三需要完成的任务。当一个流程实例完成后,ACT_RU_TASK和ACT_RU_EXECUTION表中的记录会被删除,所以我们可以通过查看ACT_RU_EXECUTION表中是否有记录来判断一个流程当前是在执行中还是完成中。代码如下:@Testvoidtest04(){StringpId="9c8557dd-3727-11ed-9404-acde48001122";ProcessInstancepi=runtimeService.createProcessInstanceQuery().processInstanceId(pId).singleResult();if(pi==null){logger.info("{}进程执行结束",pId);}else{logger.info("{}进程正在执行",pId);}}最后,如果想查询ACT_RU_EXECUTION表中的执行实例,也是可以的,方法如下:@Testvoidtest05(){Listlist=runtimeService.createExecutionQuery().processInstanceId("6d0341c7-3729-11ed-8e4e-acde48001122").list();for(Executionexecution:list){logger.info("id:{};processInstanceId:{};name:{}",execution.getId(),execution.getProcessInstanceId(),execution.getName());}}查看执行的SQL如下:==>Preparing:SELECTRES.*,P.KEY_asProcessDefinitionKey,P.ID_asProcessDefinitionId,P.NAME_作为ProcessDefinitionName,P.VERSION_作为ProcessDefinitionVersion,P.DEPLOYMENT_ID_作为DeploymentIdfromACT_RU_EXECUTIONRESinnerjoinACT_RE_PROCDEFPonRES.PROC_DEF_ID_=P.ID_WHERERES.PROC_INST_ID_=?3729-11ed-8e4e-acde48001122(String):<==Total:2可以看到,就是在ACT_RU_EXECUTION表中查询4.删除一个流程实例如果我们要删除一个流程实例,操作方法如下如下:@Testvoidtest06(){runtimeService.deleteProcessInstance("65ab0b38-38f3-11ed-b103-acde48001122","javaboywantstodelete");}注意这里是删除正在执行的流程实例信息,会不删除历史进程信息。5、获取正在运行的主动节点,可以根据执行实例的ID查询主动节点的ID,如下:@Testvoidtest07(){Listlist=runtimeService.createExecutionQuery().list();for(Executionexecution:list){ListactiveActivityIds=runtimeService.getActivityIds(execution.getId());for(StringactiveActivityId:activeActivityIds){System.out.println("activeActivityId="+activeActivityId);这里的查询其实是在ACT_RU_EXECUTION表中,查询到的activeActivityId其实就是该表的ACT_ID字段。再看查询SQL:好了,先说流程实例,下篇继续~