jBPM4介绍
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项。
?
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 canbe obtained from the ProcessEngine which is build from a Configuration.?A?ProcessEngine?isthread safe and can be stored in a static member field.
使用默认的配置文件生成Configuration并构建ProcessEngine:
ProcessEngine processEngine = newConfiguration().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 engineobjects 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 style="line-height: 12.6pt;">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(StringtransitionName),使用指定的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 style="line-height: 12.6pt;">3,??事件类型:
a)?????????<on>元素放在根元素(<process>)中,可以指定event为start或end,表示流程的开始与结束。
b)????????<on>元素放在节点元素中,可以指定event为start或end,表示节点的进入与离开
c)?????????在Start节点中只有end事件,在End节点中只有start事件。
d)????????在<transition>元素中直接写<event-listener style="line-height: 12.6pt;">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?中配置:<propertyname="hibernate.current_session_context_class">thread</property>
6,??要使用同一个SessionFactory,且都要使用?SessionFactory.getCurrentSession()?获取?Session:
a)?????????同一个SessionFactory:SessionFactory sf =processEngine.get(SessionFactory.class)
b)????????在?BaseDaoImpl?中增加:
????????????????????????i.??????????????getSession() { returnHibernateUtils.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 linkingjavax/el/ExpressionFactory class
???????atorg.apache.jsp.WEB_002dINF.jsp.UserAction.loginUI_jsp._jspInit(loginUI_jsp.java:39)
???????atorg.apache.jasper.runtime.HttpJspBase.init(HttpJspBase.java:52)
???????atorg.apache.jasper.servlet.JspServletWrapper.getServlet(JspServletWrapper.java:159)
???????atorg.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:329)
???????atorg.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:320)
???????atorg.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 = newConfiguration().buildProcessEngine();
指定其他位置的配置文件,?请使用setResource()方法
ProcessEngine processEngine = newConfiguration()
??????.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部署流程
???Stringdeploymentid = repositoryService.createDeployment()
????.addResourceFromClasspath("org/jbpm/examples/services/Order.jpdl.xml")
????.deploy();
如没有定义流程文件的key的时候自动取name(建议设定流程的key)
<process name="Insuranceclaim" 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", newFloat(763.74));
ProcessInstance processInstance =
????executionService.startProcessInstanceByKey("ICL",variables);
9.获得正确的执行的方法(给state活动分配一个事件监听器)
<state name="wait">
??<onevent="start">
????<event-listener/>
??</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?需要指定具体的哪个类和方法,可带值,也可以返回值
?