1. jBPM4介绍
1, jBPM4.4使用Hibernate3.3.1作为引擎的持久框架。
2, BPM4.4共有18张表。
2. 准备环境
2.1. 安装流程设计器插件(Graphical Process Designer)
1, jBPM4.4包含了一个图形化设计流程的工具(GPD),它是eclipse插件,是用来设计jPDL
的图形化流程的,支持的版本为Eclipse3.5。
2, 插件所在的路径为:install/src/gpd/jbpm-gpd-site.zip。
3, 安装方法:在eclipse3.5中点击help->install new software->work with->add->Archive,选择
jbpm4插件的安装包install/src/gpd/jbpm-gpd-site.zip 进行安装。安装完成后进行重新启动Eclipse
。(在User Guide文档的2.11.2节中有详细说明)。
4, 查看是否成功安装了插件:WindowàPreference中是否有Jboss jBPM项。
(通过实验,发现不可以使用把插件目录直接放到dropins下面的方法)
5, 流程定义文件的xsd文件的路径为:JBPM_HOME/src/jpdl-4.3.xsd
2.2. 准备jBPM开发环境
先新建Eclipse工程(Java Project)。
2.2.1. 添加jBPM的jar包
1, JBPM_HOME/jbpm.jar(核心包)
2, JBPM_HOME/lib/目录中的所有jar包,以下jar包可以不添加:hsqldb.jar, postgresql.jar, mysql-connector-java.jar, servlet-api.jar, junit.jar。其中junit.jar一定不要添加,因为是3.8.2版本,与我们使用的junit4有冲突。
3, 所使用的数据库对应的驱动的jar包。
2.2.2. 添加配置文件并修改配置文件内容
1, 配置文件可以从JBPM_HOME/examples/src/中拷贝:jbpm.cfg.xml、 logging.properties、
jbpm.hibernate.cfg.xml、 jbpm.tx.hibernate.cfg.xml。
2, 修改logging.properties中的日志输出级别[t1] 为WARNING:修改配置
java.util.logging.ConsoleHandler.level=WARNING
3, 修改jbpm.hibernate.cfg.xml中的数据库连接信息,如果使用MySql,使用的方言一定要是
org.hibernate.dialect.MySQL5InnoDBDialect。(如使用MySQLDialect,在流程实例结束时会抛
异常:com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Cannot delete or update
a parent row: a foreign key constraint fails (`jbpm44_20101028/jbpm4_execution`, CONSTRAINT
`FK_EXEC_INSTANCE` FOREIGN KEY (`INSTANCE_`) REFERENCES `jbpm4_execution`
(`DBID_`))。)
4, 创建数据库表。使用Hibernate的自动建表,在jbpm.hibernate.cfg.xml中配置:
hibernate.hbm2ddl.auto=update。
说明:如果要改变jbpm.hibernate.cfg.xml的文件名称,需要做:1、从JBPM_HOME/src/中拷贝jbpm.tx.hibernate.cfg.xml放到工程的src/下,然后进行修改。2、修改jbpm.tx.hibernate.cfg.xml中的hibernate主配置文件的路径配置(指定的是相对于classpath的相对路径)。
3. API与实体
3.1. 核心API 的说明
Interacting with jBPM occurs through services. The service interfaces can be obtained from the ProcessEngine which is build from a Configuration. A ProcessEngine is thread safe and can be stored in a static member field.
使用默认的配置文件生成Configuration并构建ProcessEngine:
ProcessEngine processEngine = new Configuration().buildProcessEngine();
或是使用指定的配置文件(要放到classPath下):
ProcessEngine processEngine = new Configuration()
.setResource("my-own-configuration-file.xml")
.buildProcessEngine();
jBPM所有的操作都是通过Service完成的,以下是获取Service的方式:
RepositoryService repositoryService = processEngine.getRepositoryService();
ExecutionService executionService = processEngine.getExecutionService();
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
ManagementService managementService = processEngine.getManagementService();
说明:jBPM自己有一个IOC容器,Process engine objects defined in the configuration can also be retrieved by type (processEngine.get(Class<T>)) or by name (processEngine.get(String))。
各种Service的作用:
RepositoryService |
管理流程定义 |
ExecutionService |
执行管理,包括启动、推进、删除流程实例,设置变量等操作 |
TaskService |
任务管理(任务的查询与完成等操作) |
HistoryService |
历史管理(执行完的数据管理) |
IdentityService |
jBPM的用户、组管理 |
ManagementService |
|
3.2. 实体、实体之间的关系、表 的说明
实体 |
说明 |
对应表 |
|
Deployment |
|
|
|
ProcessDefinition |
流程定义 |
|
|
|
|
|
|
Transition |
流转、连线 |
|
|
Activity |
活动(对应jBPM3中的节点Node) |
|
|
ActivityBehaviour |
|
|
|
ExternalActivityBehaviour |
|
|
|
TaskActivity |
|
|
|
|
|
|
|
Execution |
|
|
|
ProcessInstance |
流程实例 |
|
|
|
|
|
|
TaskDefinition |
任务定义 |
|
|
Task |
任务(对应jBPM3中的任务实例) |
|
|
|
|
|
|
4. 系统的学习
4.1. 管理流程(定义)
所有的流程定义的操作都是使用RepositoryService实现的。
4.1.1. 部署流程定义
RepositoryService.createDeployment()
说明:
1, 再调用.addResourceFromClasspath(resource); 可以调用多次以添加多个文件。文件重复添加也不会报错。
2, 再调用.addResourceFromInputStream(resourceName, inputStream)添加一个文件(使用InputStream)
3, .addResourcesFromZipInputStream(zipInputStream)添加一个zip文件,里面有文件夹也没关系。就相当于一次添加了多个文件
4, 以上方法可以在一起调用。
5, 一个文件添加多次,则都会存储,但只是一个流程定义。
6, 如果name指定为中文,则key就会为一个汉字变为一个"_"。
4.1.2. 查询流程定义
RepositoryService().createProcessDefinitionQuery()
说明:
1, 调用.list()查询一个列表,或调用.uniqueResult()查询一个唯一的结果。
2, 排序:.orderAsc(ProcessDefinitionQuery.PROPERTY_NAME),其中属性名为常量
要完成的功能:
1, 查询所有
2, 查询所有最新的版本(要先查询出所有,再自己处理,只保留最新的版本)
4.1.3. 删除流程定义
1, 删除一个:RepositoryService.deleteDeployment(deploymentId),删除流程定义
2, 删除一个:RepositoryService.deleteDeploymentCascade(deploymentId),删除流程定义,且删除关联的流程实例与历史信息
3, 删除指定名称的所有版本:需要先查出来,再一一删除。
4.1.4. 获取文件内容
RepositoryService().getResourceAsStream(deploymentId, resourceName);
说明:
1, resourceName是相对路径,或是部署时指定的名称。
2, 如部署时使用.addResourceFromClasspath("cn/itcast/test.jpdl.xml"),则获取时指定名称为:cn/itcast//test.jpdl.xml。如果是zip,则是在zip中的一个相对于根的相对路径。
4.1.5. 获取某节点的坐标
RepositoryService().getActivityCoordinates(processDefinitionId, activityName);
4.2. 执行流程(实例)
要使用到的Service有:ExecutionService与TaskService。
4.2.1. 启动流程实例
1, ExecutionService.startProcessInstanceById(processDefinitionId),使用唯一的pdId查询并启动
2, ExecutionService.startProcessInstanceById(processDefinitionId, variables),可设置变量
3, ExecutionService.startProcessInstanceByKey(processDefinitionKey),使用指定key(name)的最新的版本启动
4, ExecutionService.startProcessInstanceByKey(processDefinitionKey, variables),可设置变量
说明:流程实例创建后,直接就到开始节点后的第一个节点,不会在开始节点停留。
4.2.2. 获取个人任务列表:
方式1:TaskService().findPersonalTasks(userId);
方式2,TaskService().createTaskQuery()//
.assignee(userId)//
.page(0, 1000)//
.list();
4.2.3. 办理任务(完成任务)
1, TaskService().completeTask(taskId);
4.2.4. 获取流程变量
1,TaskService.getVariable(taskId, variableName);
2,ExecutionService.getVariable(executionId, variableName);
4.2.5. 拾取任务
1, TaskService.takeTask(taskId, userId),如果任务有assignee,则会抛异常。
2, processEngine.getTaskService().assignTask(taskId, userId),转交任务
4.3. 设计流程(画流程图)
4.3.1. Transition
1,一个节点中可以指定一个或多个Transition(End节点中没有)
2,如果只有一个,则可以不指定名称
3,如果有多个,则要分别指定唯一的名称
4,signal时,如果没有指定signalName/transitionName,则代表要使用没有名称的Transition,如果没有,就报错。
5,signal时,可以传递一个参数signalName,代表使用指定的Transition离开,如果没有,就报错。
6,完成任务时,completeTask()将使用默认的Transiton,completeTask(String)将使用指定的Transition。
4.3.2. 活动(节点)
4.3.2.1. 预定义节点
|
|
说明 |
Start |
开始 |
启动流程后会自动离开开始节点 |
End、EndError、EndCancel |
结束节点 |
后两个表示不同的状态 |
Task |
|
可以不指定assignee,也不会向下执行,而是等待。 |
Decision |
|
1,使用expression,如:expr="#{'to state2'}" 2,使用Handler,要实现DecisionHandler接口 3,如果同时配置了expression与Handler,则expression有效,忽略Handler。 |
Fork、Join |
分支与合并 |
测试时要注意: 1,pi.findActiveActivityNames()可以使用,但要保证是最新状态,也就是要先getById一下。 2,流程实例执行完后,用ExecutionService就查不到了 |
State |
状态 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
分配任务
1, actor=#{String型的变量}
2, AssignmentHandler,需要在<task>元素中写<assignment-handler class="AssignmentHandlerImpl"/>子元素。
a) 指定的类要实现AssignmentHandler接口
b) 在其中可以使用Assignable.setAssignee(String),分配个人任务。
4.3.2.2. 自定义节点:Custom
1,在<custom>元素中指定class属性为指定的类。
2,这个类要实现ExternalActivityBehaviour接口,其中有两个方法:
1,execute(ActivityExecution),节点的功能代码
2,signal(ActivityExecution, String, Map),在当前节点等待时,外部发信号时的行为
3,在execute()方法中,可以调用以下方法对流程进行控制
1,ActivityExecution.waitForSignal(),在当前节点等待。
2,ActivityExecution.takeDefaultTransition(),使用默认的Transition离开,当前节点中定义的第一个为默认的。
3,ActivityExecution.take(String transitionName),使用指定的Transition离开
4,ActivityExecution.end(),结束流程实例
4,也可以实现ActivityBehaviour接口,只有一个方法execute(ActivityExecution),这样就不能等待,否则signal时会有类转换异常。
4.3.3. 事件
4.3.3.1. 事件种类(有多少种事件)
4.3.3.2. 为事件配置动作(事件触发时做什么事)
1, 在根元素中,或在节点元素中,使用<on event="">元素指定事件,其中event属性代表事件的类型。
2, 在<on>中用子元素<event-listener class="EventListenerImpl" />,指定处理的类,要求指定的类要实现EventListener接口
3, 事件类型:
a) <on>元素放在根元素(<process>)中,可以指定event为start或end,表示流程的开始与结束。
b) <on>元素放在节点元素中,可以指定event为start或end,表示节点的进入与离开
c) 在Start节点中只有end事件,在End节点中只有start事件。
d) 在<transition>元素中直接写<event-listener class="">,就是配置事件。(因为在这里只有一个事件,所以不用写on与类型)
e) 在<task>元素中还可以配置assign事件,是在分配任务时触发的。
5. 附录
5.1. 常见问题说明
5.1.1. 自行控制事务
4, 修改 jbpm.tx.hibernate.cfg.xml
a) 不自行管理事务:去掉<standard-transaction-interceptor />
b) 让Jbpm使用SessionFactory.getCurrentSession():修改为 <hibernate-session current="true" />
5, 配置可以使用SessionFactory.getCurrentSession(),在jbpm.hibernate.cfg.xml 中配置:<property name="hibernate.current_session_context_class">thread</property>
6, 要使用同一个SessionFactory,且都要使用 SessionFactory.getCurrentSession() 获取 Session:
a) 同一个SessionFactory:SessionFactory sf = processEngine.get(SessionFactory.class)
b) 在 BaseDaoImpl 中增加:
i. getSession() { return HibernateUtils.getSessionFactory().getCurrentSession(); }
ii. getProcessEngine(){ return org.jbpm.api.Configuration.getProcessEngine(); }
7, 统一的打开与提交或回滚事务:使用 OpenSessionInViewFilter 控制事务。
5.1.2. 启动Tomcat时(使用的是MyEclipse自带的Tomcat,是6.0的版本),报错: Caused by: java.lang.LinkageError: loader constraints violated when linking javax/el/ExpressionFactory class
at org.apache.jsp.WEB_002dINF.jsp.UserAction.loginUI_jsp._jspInit(loginUI_jsp.java:39)
at org.apache.jasper.runtime.HttpJspBase.init(HttpJspBase.java:52)
at org.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:159)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:329)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:320)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:266)
... 40 more
说明:原因是Jbpm的juel.jar, juel-engine.jar, juel-impl.jar包和Tomcat6.0中的el-api.jar包冲突了。
有三个解决办法:
1,方法一:在MyEclipse的Preferences -> MyEclipse -> Application Servers -> Tomcat -> .. -> Paths 中配置Append to classpath,
选中 juel.jar, juel-engine.jar, juel-impl.jar 这三个jar包就可以了。
2,方法二:将 juel.jar, juel-engine.jar, juel-impl.jar 这三个包复制到tomcat6下 lib/ 中,并删除原来的el-api.jar,
切记还要把工程中 WEB-INF\lib 下的 juel.jar, juel-engine.jar, juel-impl.jar 删除,不然还是要冲突。
3,方法三:换成tomcat5.5,就没有问题了。
==========================
5.1.3. 完成流程实例中的最后一个任务时(任务实例结束时),或删除流程定义级联删除流程实例时,报错如下:
com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`/jbpm4_execution`, CONSTRAINT `FK_EXEC_INSTANCE` FOREIGN KEY (`INSTANCE_`) REFERENCES `jbpm4_execution` (`DBID_`))
解决办法:把方言设为 MySQL5InnoDBDialect,不能是 MySQLDialect。
5.2. 其他说明
1,Jbpm4的所有实体的数据库中用的主键都long型。
2,各种Service中的查询为createXxQuery(),调用list()查询一个列表,或调用uniqueResult()查询一个唯一的结果
3,查询支持分页,方法为:xxQuery.count() 与 xxQuery.page(int, int).list()。
4,以下都是String型:
1,deploymentId
2,processDefinitionId
3,executionId
4,taskId
[t1]在java.util.logging.Level的javadoc中列出了可配置的所有输出级别为(由高到低):
· SEVERE (highest value)
· WARNING
· INFO
· CONFIG
· FINE
· FINER
· FINEST (lowest value)
1.ProcessEngine流程引擎
通过classpath根目录下 默认的配置文件jbpm.cfg.xml创建一个ProcessService
ProcessEngine processEngine = new Configuration().buildProcessEngine();
指定其他位置的配置文件, 请使用setResource()方法
ProcessEngine processEngine = new Configuration()
.setResource("my-own-configuration-file.xml")
.buildProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
ExecutionService executionService = processEngine.getExecutionService();
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
ManagementService managementService = processEngine.getManagementService();
2.Deploying a process部署流程
String deploymentid = repositoryService.createDeployment()
.addResourceFromClasspath("org/jbpm/examples/services/Order.jpdl.xml")
.deploy();
如没有定义流程文件的key的时候自动取name(建议设定流程的key)
<process name="Insurance claim" key="ICL">
...
</process>
3.删除流程定义
repositoryService.deleteDeployment(deploymentId);
4.最新的流程实例(查找 key为ICL的最新版本的流程定义, 然后在最新的流程定义里启动流程实例,当部署了一个新版本, startProcessInstanceByKey方法会自动切换到 最新部署的版本)
ProcessInstance processInstance = executionService.startProcessInstanceByKey("ICL");
5.根据特定的版本启动流程实例,可以使用流程定义的id启动流程实例
ProcessInstance processInstance =
executionService.startProcessInstanceById("ICL-1");
6.新启动的流程实例分配一个key(业务key)(建议定义一个key)
ProcessInstance processInstance =
executionService.startProcessInstanceByKey("ICL", "CL92837");
7.如果没有提供用户定义的key,数据库就会把主键作为key。 这样可以使用如下方式获得id:(不建议使用搜索,太耗资源)
ProcessInstance processInstance =
executionService.startProcessInstanceByKey("ICL");
String pid = processInstance.getId();
8.使用变量(当一个新的流程实例启动时就会提供一组对象参数。 将这些参数放在variables变量里, 然后可以在流程实例创建和启动时使用)
Map<String,Object> variables = new HashMap<String,Object>();
variables.put("name", "tension");
variables.put("type", "Accident");
variables.put("amount", new Float(763.74));
ProcessInstance processInstance =
executionService.startProcessInstanceByKey("ICL", variables);
9.获得正确的执行的方法(给state活动分配一个事件监听器)
<state name="wait">
<on event="start">
<event-listener class="org.jbpm.examples.StartExternalWork" />
</on>
</state>
在这个时间监听器里,你也可以通过execution.getId()获得确切的流程id,执行完后需要signal继续运行
executionService.signalExecutionById(executionId);
10.获得用户的任务列表
List<Task> taskList = taskService.findPersonalTasks("tension");
11.读取任务数据variables
Set<String> variableNames = taskService.getVariableNames(taskId);
variables = taskService.getVariables(taskId, variableNames);
12.写入任务数据variables
variables = new HashMap<String, Object>();
variables.put("category", "small");
variables.put("lires", 923874893);
taskService.setVariables(taskId, variables);
13.完成任务
taskService.completeTask(taskId);
taskService.completeTask(taskId, variables);
taskService.completeTask(taskId, outcome);
taskService.completeTask(taskId, outcome, variables);
//Outcome:决定哪个外出转移会被选中,逻辑如下
如果一个任务拥有一个没用名称的外向转移:
· taskService.getOutcomes() 返回包含一个null值集合,。
· taskService.completeTask(taskId) 会使用这个外向转移。
· taskService.completeTask(taskId, null) 会使用这个外向转移。
· taskService.completeTask(taskId, "anyvalue") 会抛出一个异常。
如果一个任务拥有一个有名字的外向转移:
· gtaskService.getOutcomes() 返回包含这个转移名称的集合。
· taskService.completeTask(taskId) 会使用这个单独的外向转移。
· taskService.completeTask(taskId, null) 会抛出一个异常(因为这里没有无名称的转移)。
· taskService.completeTask(taskId, "anyvalue") 会抛出一个异常。
· taskService.completeTask(taskId, "myName") 会根据给定的名称使用转移。
如果一个任务拥有多个外向转移,其中一个转移没有名称,其他转移都有名称:
· taskService.getOutcomes() 返回包含一个null值和其他转移名称的集合。
· taskService.completeTask(taskId) 会使用没有名字的转移。
· taskService.completeTask(taskId, null) 会使用没有名字的转移。
· taskService.completeTask(taskId, "anyvalue") 会抛出异常。
· taskService.completeTask(taskId, "myName") 会使用名字为'myName'的转移。
如果一个任务拥有多个外向转移,每个转移都拥有唯一的名字:
· taskService.getOutcomes() 返回包含所有转移名称的集合。
· taskService.completeTask(taskId) 会抛出异常,因为这里没有无名称的转移。
· taskService.completeTask(taskId, null) 会抛出异常,因为这里没有无名称的转移。
· taskService.completeTask(taskId, "anyvalue") 会抛出异常。
· taskService.completeTask(taskId, "myName") 会使用名字为'myName'的转移。
14.查找某一特定流程定义的所有流程实例
List<HistoryProcessInstance> historyProcessInstances = historyService
.createHistoryProcessInstanceQuery()
.processDefinitionId("ICL-1")
.orderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME)
.list();
或者单独的活动
List<HistoryActivityInstance> histActInsts = historyService
.createHistoryActivityInstanceQuery()
.processDefinitionId("ICL-1")
.activityName("a")
.list();
15.获得所有已经执行的节点列表
List<HistoryActivityInstance> histActInsts = historyService
.createHistoryActivityInstanceQuery()
.processInstanceId("ICL.12345")
.list();
上面的查询与通过execution id查询有一些不同。有时execution id和流程实例id是不同的, 当一个节点中使用了定时器,execution id中就会使用额外的后缀, 这就会导致当我们通过execution id查询时, 这个节点不会出现在结果列表中。
16.返回指定流程定义的所有流程实例并分页
List<ProcessInstance> results = executionService.createProcessInstanceQuery()
.processDefinitionId("my_process_definition")
.notSuspended()
.page(0, 50)
.list();
17.获得所有查询任务并分页
List<Task> myTasks = taskService.createTaskQuery()
.processInstanceId(piId)
.assignee("tension")
.page(100, 120)
.orderDesc(TaskQuery.PROPERTY_DUEDATE)
.list();
18.
Decision的expr是指流程下一个Transition的名称
Task的assignee指的是用户
Java 需要指定具体的哪个类和方法,可带值,也可以返回值