学习jBPM工作流,最鼓舞人心的就是与数据库的交互了,就是把流程运行时状态持久化到数据库中。这一块,在jBPM3.1.2中已经用的很习惯了(其实也就是配置下持久化环境,这个公司项目前辈已经搭建好了,开发就直接调service就OK了),现在一下子跳到jBPM5.4版本了,要尝试自己来配置环境,实在是饭馆里洗碗生亲自下厨啊,,也没什么啦,只要有菜谱,做的还是八九不离十的嘛。我们就参考官方文档来学习吧。为了便于大家理解,还是翻译成中文吧(英语其实很菜的):
本段技术资料摘自于:http://docs.jboss.org/jbpm/v5.4/userguide/,请不要用于任何商业用途!!
要配置jBPM引擎来使用持久化,通常在创建session的时候可以通过使用适当的构造器。有很多方式来创建session(为了尽可能的为你简化提供了几个工具类给你,在你编写一个流程单元测试的时候可以用到)。
最简单的方式是使用jbpm-test模块来轻松创建和测试流程。JBPMHelper类有个创建session的方法,并使用一个配置文件来配置session,配置包括是否想使用持久化、数据源等等。这个帮助类接下来会为你做所有的安装和配置。
要配置持久化,要创建一个jBPM.properties文件并配置下面的属性(注意下面给的例子是默认的配置,使用H2内存数据库来启用持久化,如果你感觉这些配置很好了,就不要再添加新的配置文件,默认就用到这些属性)。
# 用于创建数据源 persistence.datasource.name=jdbc/jbpm-ds persistence.datasource.user=sa persistence.datasource.password= persistence.datasource.url=jdbc:h2:tcp://localhost/~/jbpm-db persistence.datasource.driverClassName=org.h2.Driver # 用于配置session持久化 persistence.enabled=true persistence.persistenceunit.name=org.jbpm.persistence.jpa persistence.persistenceunit.dialect=org.hibernate.dialect.H2Dialect # 用于配置人工任务服务 taskservice.enabled=true taskservice.datasource.name=org.jbpm.task taskservice.transport=mina taskservice.usergroupcallback=org.jbpm.task.service.DefaultUserGroupCallbackImpl
如果你要使用持久化,你必须确保数据源(在jBPM.properties文件中指定的)正确初始化。这意味着数据库本身要启动并运行,并且数据库应该使用正确的名称来注册。如果你想要使用H2内存数据库(通常最易于做一些测试),你可以使用JBPMHelper类来启动它,使用下面语法:
JBPMHelper.startH2Server();
要注册数据源(这是你总要做的,尽管你不使用H2作为你的数据库,检查下面更多的选项来配置你的数据库),使用:
JBPMHelper.setupDataSource();
接下来,你可以使用JBPMHelper类来创建session(先要创建知识库,这个和不使用持久化的情况是一样的):
StatefulKnowledgeSession ksession = JBPMHelper.newStatefulKnowledgeSession(kbase);
这些都做好了,你只要调用这个ksession上的方法(如startProcess)然后引擎会在创建好的数据源中持久化所有的运行时状态。
你也可以使用JBPMHelper类来重新创建session(通过从数据库恢复它的状态,只需要传递session id(id可使用ksession.getId()方法获取到)):
StatefulKnowledgeSession ksession = JBPMHelper.loadStatefulKnowledgeSession(kbase, sessionId);
你也可以使用JPAKnowledgeService来创建知识会话。这稍微复杂些,但会让你访问到所有的优先配置。你可以使用基于知识库、知识会话配置(如果必要)和环境的JPAKnowledgeService来创建一个新的知识会话。环境需要包含对你的实体管理器工厂的引用,例如:
// 创建实体管理器工厂并在环境中注册它 EntityManagerFactory emf = Persistence.createEntityManagerFactory( "org.jbpm.persistence.jpa" ); Environment env = KnowledgeBaseFactory.newEnvironment(); env.set( EnvironmentName.ENTITY_MANAGER_FACTORY, emf ); // 创建一个新的知识会话来使用JPA存储运行时状态 StatefulKnowledgeSession ksession = JPAKnowledgeService.newStatefulKnowledgeSession( kbase, null, env ); int sessionId = ksession.getId(); // 这儿调用你自己的方法 ksession.startProcess( "MyProcess" ); ksession.dispose();
你也可以使用JPAKnowledgeService来根据一个指定的session id重新创建session:
// 使用sessionId从数据库恢复session ksession = JPAKnowledgeService.loadStatefulKnowledgeSession( sessionId, kbase, null, env );
你需要添加持久化配置到你的类路径下,以便配置JPA使用Hibernate和H2数据库(或其他数据库),要创建一个META-INF目录来存放一个persistence.xml文件,显示如下。更多关于如何修改这些配置,可以从JPA和Hibernate文档中获取更多信息。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence version="1.0" xsi:schemaLocation= "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="org.jbpm.persistence.jpa" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>jdbc/jbpm-ds</jta-data-source> <mapping-file>META-INF/JBPMorm.xml</mapping-file> <class>org.drools.persistence.info.SessionInfo</class> <class>org.jbpm.persistence.processinstance.ProcessInstanceInfo</class> <class>org.drools.persistence.info.WorkItemInfo</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <property name="hibernate.max_fetch_depth" value="3"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup"/> </properties> </persistence-unit> </persistence>
这个配置文件指向的数据源叫做“jbdc/jbpm-ds”。如果在你的应用服务器中运行应用(例如JBoss AS),这些容器可以使用自己的一些配置来简单建立数据源(例如在部署目录中放一个数据源配置文件)。可以参考你的应用服务器的文档来获取如何配置这些。
例如,如果你部署应用到JBoss 应用服务器 v5.x中,你可以在部署目录中丢进一个配置文件来创建数据源,如:
<?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>jdbc/jbpm-ds</jndi-name> <connection-url>jdbc:h2:tcp://localhost/~/test</connection-url> <driver-class>org.h2.jdbcx.JdbcDataSource</driver-class> <user-name>sa</user-name> <password></password> </local-tx-datasource> </datasources>
如果你在Java环境中执行的话,你可以使用JBPMHelper类来做这个(如上)或者可用下面的代码段来建立数据源(这儿我们把Bitronix和H2内存数据库结合使用了)。
PoolingDataSource ds = new PoolingDataSource(); ds.setUniqueName("jdbc/jbpm-ds"); ds.setClassName("bitronix.tm.resource.jdbc.lrc.LrcXADataSource"); ds.setMaxPoolSize(3); ds.setAllowLocalTransactions(true); ds.getDriverProperties().put("user", "sa"); ds.getDriverProperties().put("password", "sasa"); ds.getDriverProperties().put("URL", "jdbc:h2:tcp://localhost/~/jbpm-db"); ds.getDriverProperties().put("driverClassName", "org.h2.Driver"); ds.init();
上面的配置就是建立持久化配置,在流程执行的时候可以把状态保存到数据库中,并在需要的时候再从数据库恢复状态继续执行。大体就这么理解吧。
好啦,我们就参照上面的这段文档说明,自己来建一个实例试一试吧。
1. 启动Eclipse,建一个Java Project,先准备基本的环境:
项目基本结构如下:
Test
src
com.test
Test01.java
hello.bpmn
jBPM.properties
JRE...
jBPM5.4
junit
2. Test01.java
public class Test01 { @Test public void test() { try { // 读取流程定义,获取知识库对象 KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add(ResourceFactory.newClassPathResource("com/test/hello.bpmn"), ResourceType.BPMN2); KnowledgeBase kbase = kbuilder.newKnowledgeBase(); // 启动h2数据库 org.h2.tools.Server server = JBPMHelper.startH2Server(); //JBPMHelper.startUp(); // 建立数据源 bitronix.tm.resource.jdbc.PoolingDataSource s = JBPMHelper.setupDataSource(); //System.out.println(s); // 创建有状态知识会话 StatefulKnowledgeSession ksession = JBPMHelper.newStatefulKnowledgeSession(kbase); int id = ksession.getId(); System.out.println(id); // 开始流程执行,会进行持久化 ksession.startProcess("com.sample.bpmn"); // 从数据库加载流程 StatefulKnowledgeSession ksession2 = JBPMHelper.loadStatefulKnowledgeSession(kbase, id); System.out.println(ksession2); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
3. 运行说明
这个程序中的流程我们设计的很简单,就一个开始节点、一个结束节点和中间的一个脚本任务节点。
关键是配置持久化,这里面我们使用了jBPM提供的一个帮助类JBPMHelper来在测试中进行持久化,
这个就很简单了,我们可以使用默认的持久化配置,也就数这里的jBPM.properties文件,我们其实没有必要覆盖默认定义的(在相应的jar包里可以找到这个定义文件),数据源配置都是默认的,很简单的。
如果我们想要手动进行持久化的配置,我们想使用自己额数据库、想使用服务器提供的数据源,这些都可以参照上面的文档来实现,这里我们还是基于jbpm-test,做一个最简单的测试,还是使用JBPMhelper来启动H2数据库并建立数据源,但是这里我们就不能使用它默认加载的jBPM.properties文件了,我们要在类路径下建一个META-INF目录,里面放一个persistence.xml文件,这个文件很简单:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <persistence version="1.0" xsi:schemaLocation= "http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="org.jbpm.persistence.jpa" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>jdbc/jbpm-ds</jta-data-source> <mapping-file>META-INF/JBPMorm.xml</mapping-file> <class>org.drools.persistence.info.SessionInfo</class> <class>org.jbpm.persistence.processinstance.ProcessInstanceInfo</class> <class>org.drools.persistence.info.WorkItemInfo</class> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/> <property name="hibernate.max_fetch_depth" value="3"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup"/> </properties> </persistence-unit> </persistence>
这个配置我们再熟悉不过了,持久化配置都有了,这里还会加载一个ProcessInstanceInfo实例,这个就是流程实例持久化对象,当然了因为使用的是Hibernate操作,肯定要提供query模块了还有mapping配置,这些在jBPM持久化的相关jar包中都有了,包括JBPMorm.xml和ProcessInstanceInfo.hbm.xml,我们可以使用默认的配置,没有必要添加进来了。
JBPMorm.xml:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm orm_1_0.xsd"
version="1.0">
<named-query name="ProcessInstancesWaitingForEvent">
<query>
select
processInstanceInfo.processInstanceId
from
org.jbpm.persistence.processinstance.ProcessInstanceInfo processInstanceInfo join processInstanceInfo.eventTypes eventTypes
where
eventTypes = :type
</query>
</named-query>
</entity-mappings>
ProcessinstanceInfo.hbm.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" > <hibernate-mapping package="org.jbpm.persistence.processinstance"> <!-- access="field" for fields that have no setter methods --> <class name="ProcessInstanceInfo" table="ProcessInstanceInfo"> <id name="processInstanceId" type="long" column="InstanceId"> <generator class="native" /> </id> <version name="version" type="integer" unsaved-value="null" access="field"> <column name="OPTLOCK" not-null="false" /> </version> <property name="processId" access="field" /> <property name="startDate" type="timestamp" access="field" /> <property name="lastReadDate" type="timestamp" access="field" /> <property name="lastModificationDate" type="timestamp" access="field" /> <property name="state" type="integer" not-null="true" access="field" /> <property name="processInstanceByteArray" type="org.hibernate.type.PrimitiveByteArrayBlobType" column="processInstanceByteArray" access="field" length="2147483647" /> <set name="eventTypes" table="EventTypes" access="field"> <key column="InstanceId" /> <element column="element" type="string" /> </set> <!-- NOT mapping [processInstance] field because field is transient --> <!-- NOT mapping [env] field because field is transient --> </class> </hibernate-mapping>
注意了!!上述的两个文件我在未手动添加到项目classpath的时候执行流程报错了:
a PoolingDataSource containing an XAPool of resource jdbc/jbpm-ds with 0 connection(s) (0 still available) 0 01/09 10:34:25,158[main] ERROR hibernate.impl.SessionFactoryImpl.<init> - Error in named query: ProcessInstancesWaitingForEvent org.hibernate.hql.ast.QuerySyntaxException: ProcessInstanceInfo is not mapped [select processInstanceInfo.processInstanceId from ProcessInstanceInfo processInstanceInfo join processInstanceInfo.eventTypes eventTypes where eventTypes = :type] at org.hibernate.hql.ast.util.SessionFactoryHelper.requireClassPersister(SessionFactoryHelper.java:181) at org.hibernate.hql.ast.tree.FromElementFactory.addFromElement(FromElementFactory.java:110) at org.hibernate.hql.ast.tree.FromClause.addFromElement(FromClause.java:94) at org.hibernate.hql.ast.HqlSqlWalker.createFromElement(HqlSqlWalker.java:316) at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3228) at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3112) at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:720) at org.hibernate.hql.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:571) at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:288) at org.hibernate.hql.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:231) at org.hibernate.hql.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:254) at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:185) at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136) at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:101) at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:80) at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:94) at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:484) at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:394) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1341) at org.hibernate.cfg.AnnotationConfiguration.buildSessionFactory(AnnotationConfiguration.java:867) at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:669) at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:126) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:52) at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:34) at com.test.Test01.test(Test01.java:41) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222) at org.junit.runners.ParentRunner.run(ParentRunner.java:300) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
后来自己研究发现:在JBPMorm.xml文件中的查询语句要把ProcessInstanceInfo指定为完全限定类名!!我们再把这两个文件添加到classpath中,运行就OK了,
a PoolingDataSource containing an XAPool of resource jdbc/jbpm-ds with 0 connection(s) (0 still available) Hibernate: insert into SessionInfo (id, lastModificationDate, rulesByteArray, startDate, OPTLOCK) values (null, ?, ?, ?, ?) 36 Hibernate: insert into ProcessInstanceInfo (InstanceId, OPTLOCK, processId, startDate, lastReadDate, lastModificationDate, state, processInstanceByteArray) values (null, ?, ?, ?, ?, ?, ?, ?) Hibernate: select processins0_.InstanceId as col_0_0_ from ProcessInstanceInfo processins0_ inner join EventTypes eventtypes1_ on processins0_.InstanceId=eventtypes1_.InstanceId where eventtypes1_.element=? Hibernate: update SessionInfo set lastModificationDate=?, rulesByteArray=?, startDate=?, OPTLOCK=? where id=? and OPTLOCK=? Hibernate: delete from ProcessInstanceInfo where InstanceId=? and OPTLOCK=? Hibernate: select sessioninf0_.id as id2_0_, sessioninf0_.lastModificationDate as lastModi2_2_0_, sessioninf0_.rulesByteArray as rulesByt3_2_0_, sessioninf0_.startDate as startDate2_0_, sessioninf0_.OPTLOCK as OPTLOCK2_0_ from SessionInfo sessioninf0_ where sessioninf0_.id=? org.drools.command.impl.CommandBasedStatefulKnowledgeSession@3730ebf6
最后的测试程序是:
public class Test01 { @Test public void test() { org.h2.tools.Server server = JBPMHelper.startH2Server(); bitronix.tm.resource.jdbc.PoolingDataSource s = JBPMHelper.setupDataSource(); System.out.println(s); KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add(ResourceFactory.newClassPathResource("com/test/hello.bpmn"), ResourceType.BPMN2); KnowledgeBase kbase = kbuilder.newKnowledgeBase(); // 创建实体管理器工厂并在环境中注册它 EntityManagerFactory emf = Persistence.createEntityManagerFactory( "org.jbpm.persistence.jpa" ); Environment env = KnowledgeBaseFactory.newEnvironment(); env.set( EnvironmentName.ENTITY_MANAGER_FACTORY, emf ); // 创建一个新的知识会话来使用JPA存储运行时状态 StatefulKnowledgeSession ksession = JPAKnowledgeService.newStatefulKnowledgeSession( kbase, null, env ); int sessionId = ksession.getId(); System.out.println(sessionId); // 这儿调用你自己的方法 ksession.startProcess( "com.sample.bpmn" ); ksession.dispose(); StatefulKnowledgeSession ksession2 = JPAKnowledgeService.loadStatefulKnowledgeSession(sessionId, kbase, null, env); System.out.println(ksession2); } }
也是比较简单的,后面我们再在持久化配置的基础上介绍如何进行事务的配置吧,。