对
Jbpm
数据库应用的简单分析和在
Mysql
上实现的
demo
吴大愚
dywu_xa@sina.com
2006-10-17
适用
jbpm3.1
版本
对
HelloWorldDbTest
的分析
在
Eclipse
中到如
jbpm-starters-kit-3.1\jbpm
工程后,在
src\examples\org\jbpm\db
底下有一个
HelloWorldDbTest.java
的实例。我从这个实例分析开始,构建自己的
jbpm
工程在
Mysql
下的应用。
首先我们来先分析一下
HelloWorldDbTest
实例。
基本流程
HelloWorldDbTest
类是一个
JUnit
的测试类。里面只有一个测试函数
testSimplePersistence
。这个函数里面调用了三个操作,分别是:
l
将流程(
processDefinition
)的部署到数据库中;
l
从数据库中加载流程,并实例化一个流程实例(
processInstance
),然后运行一半再将流程实例存回数据库;
l
从数据库中加载流程实例,然后运行完毕这个实例。
JbpmConfigration
在类
HelloWorldDbTest
中有静态代码段,内容是构造一个
JbpmConfigration
的实例。
JbpmConfigration
在
Jbpm3.1
中和
Jbpm3
中差别很大,
JbpmAPI
手册描写如下:
“
configuration of one jBPM instance. During process execution, jBPM might need to use some services. A JbpmConfiguration contains the knowledge on how to create those services.
A JbpmConfiguration is a thread safe object and serves as a factory for JbpmContexts, which means one JbpmConfiguration can be used to create JbpmContexts for all threads. The single JbpmConfiguration can be maintained in a static member or in the JNDI tree if that is available.
”
JbpmConfigration
主要有两种加载方法,一种是从配置文件中加载,一般使用
jbpm.cfg.xml
,
jbpm-starters-kit-3.1
中放在
jbpm-starters-kit-3.1\jbpm\src\config.files
目录下。要使用
jbpm.cfg.xml
进行配置的话,就必须把这个文件放在
classpath
中。因为
jbpm-starters-kit-3.1\jbpm
工程已经把此目录放在工程的
classpath
中,所以如果在代码中
JbpmConfiguration jbpmConfiguration = JbpmConfiguration.getInstance();
这样书写的话就是使用
jbpm.cfg.xml
中的配置来构造
JbpmConfigration
。另外一种就是本例中使用的对
xml
解析的方法。
使用数据库
在加载的
JbpmConfiguration
的加载内容中有如下部分:
“
" <string name='resource.hibernate.cfg.xml' " +
" value='hibernate.cfg.xml' />" +
”
这里面的
hibernate.cfg.xml
也在
jbpm-starters-kit-3.1\jbpm\src\config.files
目录下,是对
Jbpm
使用的
Hibernate
的配置文档。通过这个文档,
Jbpm
就可以通过
Hibernate
的工具,在不同的数据库中,把说使用到的表全部构建出来,而不需要再人工去操作
sql
语句在数据库中操作,这也就是为什么这个实例中没有需要我们自己在数据库中建表建库的原因。
通过
HelloWorldDbTest
类的注释和用户手册中的文档可以知道,在这个实例中使用的数据库是一个内存数据库,在目录下的
Hibernate.cfg.xml
中有如下内容:
“
<!-- jdbc connection properties -->
<
property
name
=
"hibernate.dialect"
>
org.hibernate.dialect.HSQLDialect
</
property
>
<
property
name
=
"hibernate.connection.driver_class"
>
org.hsqldb.jdbcDriver
</
property
>
<
property
name
=
"hibernate.connection.url"
>
jdbc:hsqldb:mem:.;sql.enforce_strict_size=true
</
property
>
<
property
name
=
"hibernate.connection.username"
>
sa
</
property
>
<
property
name
=
"hibernate.connection.password"
></
property
>
”
懂得
Hibernate
的朋友就能看出来,在
jbpm
工程中,使用的数据库是
hsqldb
。
在加载
HelloWorldDbTest
类的时候,就会发现,在
console
有大量的输出。其中包括:
“
12:27:38,201 [main] INFO Configuration : Reading mappings from resource: org/jbpm/graph/def/Transition.hbm.xml
12:27:38,231 [main] INFO HbmBinder : Mapping class: org.jbpm.graph.def.Transition -> JBPM_TRANSITION
“很多类似的输出。这些输出就是
Hiernate
在加载描述文件时候产生的说明信息。
输出信息中后面还包括对数据库的连接,设置等等说明信息。
JbpmConfiguration.createSchema()
这个方法可以实现自动在数据库中生成表结构的。在这个实例中,在
Junit
的
setup()
操作中调用了这个函数,在进行测试实例前对数据库进行创建。
JbpmContext
运行
HelloWorldDbTest
中的测试实例。
首先是定义一个流程,然后就实例化一个
JbpmContext
。
“
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
“
JbpmContext
类是
Jbpm3.1
版本引进的一个新类,以前是没有的。
在
jbpm
的说明中有对
JbpmContext
类的如下说明:
JbpmContext
is used to surround persistent operations to processes.
Obtain JbpmContext's via JbpmConfiguration.createJbpmContext() and put it in a try-finally block like this:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
TaskInstance taskInstance = ...
...do your process operations...
// in case you update a process object that was not fetched
// with a ...ForUpdate method, you have to save it.
jbpmContext.save(processInstance);
finally {
jbpmContext.close();
}
A JbpmContext separates jBPM from a sprecific environment. For each service that jBPM uses, there is an interface specified in the jBPM codebase. jBPM also includes implementations that implement these services by using services in a specific environment. e.g. a hibernate session, a JMS asynchronous messaging system, ...
A JbpmContext can demarcate a transaction. When a PersistenceService is fetched from the JbpmContext, the default implementation for the persistence service will create a hibernate session and start a transaction. So that transactions can be configured in the hibernate configuration.
A JbpmContext allows the user to overwrite (or make complete) the configuration by injecting objects programmatically. like e.g. a hibernate session factory or a hibernate session or any other resource that can be fetched or created from the configuration.
Last but not least, JbpmContext provides convenient access to the most common operations such as getTaskList(String), newProcessInstance(String)loadTaskInstanceForUpdate(long) and save(ProcessInstance).
All the ...ForUpdate(...) methods will automatically save the loaded objects at jbpmContext.close();
将上面这些英文读懂,基本上就能对JbpmContext有个大致的了解。最主要的就是实现Jbpm对数据库操作的所有接口的封装。
流程部署
在
deployProcessDefinition
中的
jbpmContext. deployProcessDefinition (processDefinition);
操作,向数据库中部署一个流程。如果跟踪
deployProcessDefinition
的话,会发现
jbpmContext
还是调用的
GraphSession
的流程部署方法。后面我们再讲到
GraphSession
类。
在后面使用
Mysql
实现的例子中,我们就可以通过工具在
mysql
中看到这样一个部署流程的操作对数据库有如何影响。同样也可以查看流程实例化保存在数据库中的情况。
GraphSession
Jbpm
中
are the graph related database operations
的类。可以通过
GraphSession graphSession = jbpmContext.getGraphSession();
来得到这个类的实例,并且
JbpmContext
很多方法都是封装该类的方法。
该类中包括了几乎所有的数据库操作的,例如部署流程,加载流程,部署流程实例,加载变量,等等。
HelloWorldDbTest
类本身就已经有非常完整的注释,
有关分析也就写到这里。
Jbpm
在
Mysql
上的
demo
工程
创建
Jbpm Project
首先在
Eclipse
里面创建一个
Jbpm Process Project
的新项目。比如命名为
myDemo
。
Eclipse
的
Jbpm
插件会自动创建一些文件,比如在
processes
目录下有一个
simple
的流程。在
src/java
下面有一个
MessageActionHandler
的类。在
src/config.files
目录下四个配置有文件。在
test/java
下面有一个
SimpleProcessTest
的
JUnit
类实例。可以先运行这个测试,应该是正确没有问题的。
配置
Mysql
数据库
打开
MySql
数据库,假设
MySql
数据库的用户名为
root
,密码
1234
。首先创建
jbpm_db
数据库。然后修改
src/config.files
底下的
Hibernate.cfg.xml
文件。将连接数据库的部分换成
Mysql
的内容。如下:
<!-- jdbc connection properties -->
<
property
name
=
"hibernate.dialect"
>
org.hibernate.dialect.MySQLDialect
</
property
>
<
property
name
=
"hibernate.connection.driver_class"
>
com.mysql.jdbc.Driver
</
property
>
<
property
name
=
"hibernate.connection.url"
>
jdbc:mysql://localhost:3306/jbpm_db
</
property
>
<
property
name
=
"hibernate.connection.username"
>
root
</
property
>
<
property
name
=
"hibernate.connection.password"
>
1234
</
property
>
添加代码和流程
我们先创建一个自己的流程。在
processes
目录下面创建一个叫做
pro1
的流程。然后添加最简单的,
start-state ,end
和一个
state
节点。
将
Jbpm3
工程中的
jbpm.cfg.xml
中的内容添加到
src/config.files
目录下的
jbpm.cfg.xml
中。
在
test/java
目录下面添加一个新的
Junit
类。命名为
SimpleDBTest
。
代码如下:
package com.sample;
import java.io.FileInputStream;
import java.util.List;
import junit.framework.TestCase;
import org.jbpm.JbpmConfiguration;
import org.jbpm.JbpmContext;
import org.jbpm.db.GraphSession;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
import org.jbpm.graph.exe.Token;
public class SimpleDBTest extends TestCase {
static JbpmConfiguration cfg = JbpmConfiguration.getInstance();
public void setUp() {
cfg.createSchema();
}
public void testDeployProcessDefinition() throws Exception {
assertNotNull("JbpmConfiguration should not be null", cfg);
FileInputStream fis = new FileInputStream("processes/pro1/processdefinition.xml");
ProcessDefinition processDefinition = ProcessDefinition.parseXmlInputStream(fis);
assertNotNull("Definition should not be null", processDefinition);
JbpmContext jbpmContext = cfg.createJbpmContext() ;
try {
// Deploy the process definition in the database
jbpmContext.deployProcessDefinition(processDefinition);
} finally {
jbpmContext.close();
}
}
}
执行这个操作,会发现报错,报不能找到
Hibernate
的错误。原来在
Eclipse
建立的
Jbpm
的工程中,虽然添加了
Jbpm
的包,但没有添加
Hibernate
的包。因此在工程的
Java Build Path
的
Libraries
底下添加
Hibernate
的包(具体添加那些,请参考
Jbpm
的用户手册)。
部署流程
执行
SimpleDBTest .testDeployProcessDefinition()
操作。如果没有错误的话,这次就可以将流程
pro1
部署进入
Mysql
数据库。
在
jbpm_db
数据库中会看到产生了
33
个表。查看
jbpm_processdefinition
表,会发现有一个名字为
pro1
,
ID
为
1
,
version
为
1
的条目。
同样可以查看
jbpm_node
表。
再次部署
修改流程
pro1
的定义,在
state1
节点添加一个
Action
,在
node-enter
事件中。完整的
xml
文件见该文档最后部分。然后再次运行
SimpleDBTest .testDeployProcessDefinition()
操作。查看
jbpm_processdefinition
,会发现有了一个
pro1
的
version
为
2
版本的流程。这里有了一个
Jbpm
流程版本的概念,可以参考用户手册。
还可查看
jbpm_event
,
jbpm_action
表。
实例化流程
添加代码:
public void testLoadProcessAndInstance() throws Exception {
JbpmContext jbpmContext = cfg.createJbpmContext() ;
try {
GraphSession graphSession = jbpmContext.getGraphSession();
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("pro1");
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
Token token = processInstance.getRootToken();
assertEquals("start", token.getNode().getName());
// Let's start the process execution
token.signal();
assertEquals("state1", token.getNode().getName());
jbpmContext.save(processInstance);
} finally {
// Tear down the pojo persistence context.
jbpmContext.close();
}
}
这段代码把
pro1
流程的最新版本加载进来,然后实例化。并开始执行,到
state1
节点停下来(此时
Action
已经执行过了)。然后把这个实例也存入数据库。
这时候查看
jbpm_processInstance
表,
jbpm_token
表。
完成实例的运行
添加代码
public void testLoadInstanceAndDoActionAndEnd() throws Exception {
JbpmContext jbpmContext = cfg.createJbpmContext() ;
try {
GraphSession graphSession = jbpmContext.getGraphSession();
ProcessDefinition processDefinition =
graphSession.findLatestProcessDefinition("pro1");
List processInstances =
graphSession.findProcessInstances(processDefinition.getId());
ProcessInstance processInstance =
(ProcessInstance) processInstances.get(0);
this.assertEquals("message",(String)(processInstance.getContextInstance().getVariable("message")));
processInstance.signal();
assertTrue(processInstance.hasEnded());
jbpmContext.save(processInstance);
} finally {
jbpmContext.close();
}
}
这段代码将刚才的流程实例从数据库中加载进来,然后执行完毕。
查看表
jbpm_processinstance
表,会发现上次
end
字段还是
null
,现在已经是填写了刚才执行的事件了,表示这个流程实例已经执行完毕。
流程
Pro1
的完整
xml
文档
<?
xml
version
=
"1.0"
encoding
=
"UTF-8"
?>
<
process-definition
xmlns
=
"urn:jbpm.org:jpdl-3.1"
name
=
"pro1"
>
<
start-state
name
=
"start"
>
<
transition
name
=
""
to
=
"state1"
></
transition
>
</
start-state
>
<
state
name
=
"state1"
>
<
event
type
=
"node-enter"
>
<
action
name
=
"action1"
class
=
"com.sample.action.MessageActionHandler"
>
<
message
>
message
</
message
>
<
message2
>
message2
</
message2
>
</
action
>
</
event
>
<
transition
name
=
""
to
=
"end"
></
transition
>
</
state
>
<
end-state
name
=
"end"
></
end-state
>
</
process-definition
>
总结