关闭

JBPM入门学习

481人阅读 评论(0) 收藏 举报

1.      jBPM的简单介绍

jBPMJBOSS下的一个开源java工作流项目,该项目提供eclipse插件,基于Hibernate实现数据持久化存储。

   参考

http://www.jboss.com/products/jbpm

2.      jBPM和myeclipse的冲突

eclipse安装了myeclipsejBPM时候,可能有冲突,具体表现在jBPM的流程设计器不能在eclipse中使用。

 

3.      Hibernate连接mysql数据库的一般参数

下面的配置参数,根据需要可以修改:

jbpmtestmysql中的schema的名字;

GBK是字符集,可以根据需要修改;

username=rootmysql数据库的用户名是root

password=mysqlmysql数据库的用户密码是mysql

 

hibernate.dialect=org.hibernate.dialect.MySQLDialect

hibernate.connection.driver_class=com.mysql.jdbc.Driver

hibernate.connection.url=jdbc:mysql://localhost/jbpmtest?useUnicode=true&characterEncoding=GBK

hibernate.connection.username=root

hibernate.connection.password=mysql

 

hibernate.show_sql=true

hibernate.c3p0.min_size=1

hibernate.c3p0.max_size=3

 

4.      Hibernate连接Oracle数据库的一般参数

hibernate.dialect=org.hibernate.dialect.Oracle9Dialect

hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver

hibernate.connection.url=jdbc:oracle:thin:@localhost:1521:orcl hibernate.connection.username=jbpm

hibernate.connection.password=jbpm

 

5.      为jBPM创建数据库表等内容

5.1.             为mysql创建数据库以及数据内容

JbpmSessionFactory.buildJbpmSessionFactory().getJbpmSchema().dropSchema();

JbpmSessionFactory.buildJbpmSessionFactory().getJbpmSchema().createSchema();

2个语针对mysq有效.

5.2.             为oralce创建数据库以及数据内容

JbpmSessionFactory.buildJbpmSessionFactory().getJbpmSchema().dropSchema();

JbpmSessionFactory.buildJbpmSessionFactory().getJbpmSchema().createSchema();

上面的语句对oralce无效。

模型目录jBPM/jbpm_database/jBPM_oralce10g.pdm jBPMOralce模型,包含对应的Sequence的信息.

需要在数据库中创建 Sequence name=hibernate_sequence

http://wiki.jboss.org/wiki/Wiki.jsp?page=JbpmOnOracle

5.3.             流程信息保存到数据库

JbpmSessionFactory  factory=  JbpmSessionFactory.buildJbpmSessionFactory();

        JbpmSession session = factory.openJbpmSession();

        GraphSession graphSession =  session.getGraphSession();

        session.beginTransaction();

        

        /// new ProcessDefinition 实例

        ProcessDefinition myProcessDefinition = null;

    ProcessInstance processInstance = new ProcessInstance(processDefinition);

      processInstance.getContextInstance().setVariable("myvar","xxx");

        ///

        graphSession.saveProcessDefinition(myProcessDefinition);

        session.commitTransaction();

session.close();

jBPMHibernate,MySQL的使用中会遇到字符集的问题.

需要注意几个地方。

5.4.             在Mysql安装过程指定字符集

   在Mysql安装过程中指定相应的默认字符集是GBK

5.5.             Hibernate.properties文件中的字符集设置

hibernate.connection.url=jdbc:mysql://localhost/jbpmtest?useUnicode=true&characterEncoding=GBK

5.6.             hibernate.cfg.xml 文件中的字符集设置

<property name="hibernate.connection.url">jdbc:mysql://192.168.1.2/jbpmtest</property>

5.7.             MySQL的配置文件的修改:

   my.ini中 default-character-set=GBK

   注意有2处需要修改

 

6.      为流程定义变量

分成3种流程变量

全局变量(全局流程变量)

全局临时变量(全局流程临时变量)

局部变量(流程某个接点内有效的变量)

@see org.jbpm.context.exe.ContextInstance

目前还没有使用过局部变量(流程某个接点内有效的变量)

流程变量的定义

6.1.             流程变量的类型

·         java.lang.String

·         java.lang.Boolean

·         java.lang.Character

·         java.lang.Float

·         java.lang.Double

·         java.lang.Long

·         java.lang.Byte

·         java.lang.Short

·         java.lang.Integer

·         java.util.Date

·         byte[]

·         java.io.Serializable

·         classes that are persistable with hibernate

 所有基本类型的包裹类型,以及实现了Serializable的类型都可以作为流程参数变量使用,注意参数类不能是一个类的内部类或者属性类(除非包含参数类的类实现了Serializable接口)

参考 org.jbpm.context.exe.VariableInstance

6.2.             流程变量的使用

l         变量的定义和获取

void ContextInstance.setVariable(String variableName, Object value);

void ContextInstance.setVariable(String variableName, Object value, Token token);

Object ContextInstance.getVariable(String variableName);

Object ContextInstance.getVariable(String variableName, Token token);

l        Variables can be deleted with

ContextInstance.deleteVariable(String variableName);

ContextInstance.deleteVariable(String variableName, Token token);

 

6.3.             Variable lifetime

   一个变量在被定义后,在变量从ContextInstance删除前,都可以被访问。当变量被删除后,去获取该变量将返回为空。

6.4.             自定义类的实例作为流程变量

   如果一个类的实例要作为流程变量使用,该类需要实现java.io.Serializable接口,并且定义序列化版本.

 

//一个可以作为流程变量使用的类

classWdz implements Serializable{

    //为类指定序列化版本号

    private static final long serialVersionUID = 1L;     

       private String name="wdz";

       private int age=10;

       public String toString(){

              return "name="+name+",age="+age;     

       }     

}

上面的例子中,如果把类Wdz作为类WdzParent内部类使用,然后当成流程变量使用,那类WdzParent必须 也实现Serializable接口,否则会有问题.

6.5.             Transient variables

流程的历史变量是不能持久化的,作用范围是对整个流程全局有效的。

ContextInstance类内部,采用的是一个Map来存储TransientVariable的信息

参考代码

org.jbpm.context.exe.ContextInstance的源代码。

主要的相关方法

public void deleteTransientVariable(java.lang.String name)

public void setTransientVariable(java.lang.String name,

                                 java.lang.Object value)

publicjava.lang.Object getTransientVariable(java.lang.String name)

 

6.6.             Variables overloading

 当一个变量和流实例关联(变量名字=”wdz”,value=”test”),如果在设置一个变量

(变量名字=”wdz”,value= new Integer(10)) ,那最后变量(变量名字=”wdz)的值是Integer(10)

这称之为overload.

 

6.7.             Variables overriding

 如果父流程有变量A(=”value1”),子流程又定义了变量A=”value2”,那在子流程范围内,获取变量A的值,那结果是值=”value2”。这是遵循一般语言的局部变量在它的局部范围内override上级变量。

 

6.8.             流程变量的持久化

它依赖于流程实例的持久化,非TransientVariable随流程实例的持久化一起被保存。

保存在数据表jbpm_variableinstance

参考 org.jbpm.context.exe.VariableInstance

6.9.             Customizing variable persistence (可以进一步了解)

User java object <---> converter <--->  variable instance 

也就是自定义类的持久化需要定义自己的converter和变量实例类

converter和变量实例类需要继承org.jbpm.context.exe.VariableInstance

converter需要实现org.jbpm.context.exe.Converter接口

 

工作流虽然还在不成熟的发展阶段,甚至还没有一个公认的规范标准。但其应用却已经在快速展开,这说明市场对工作流框架的需求是急迫而巨大的。 

    我们公司的后台短信管理系统涉及短信编发、领导层层审核等操作,这是一个比较典型的工作流系统。过去我们用的工作流引擎是 shark ,然后在使用后发现其过于庞大,后台数据库操作频繁而未进行优化,直接导致的后果就是前台操作缓慢。于是经研究决定,将工作流引擎由 shark 换成 jBPM 。 

    jBPM 之前是一个开源软件,后加入 JBoss 组织。正好我们公司也是用 JBoss 的。不过 jBPM 并没有绑定在 JBOSS 上, Tomcat 等也可以使用它。 

    jBPM 的正处在不断发展中,做为开源软件的特点,其设计变化往往很大。所以一些过去的资料可能已经不适用了。于是作者根据自己的使用经验,重新整理出这份文档,以备学习参考。 

注:本文使用的 jBPM 版本为 3.1.1 

环境准备

1、安装JDK

所有 JAVA 开发第一个需要安装的,没什么好说的。记得把系统变量 JAVA_HOME 设上。 

2、安装Ant

Ant 是使用 jBPM 必须的一个工具。 jBPM 中的很多操作都要用到 Ant 。 

安装方法: 

( 1 )先下载: http://archive.apache.org/dist/ant/binaries/ ,选一个如: apache-ant-1.6.5-bin.zip 。 

( 2 )解压到 D:/ant (当然其他目录也可以)。 

( 3 )设置如下系统变量: ANT_HOME=d:/ant 。 

( 4 )把 %ANT_HOME%/bin 加入到系统变量 PATH 中。 

3、安装Eclipse

Eclipse 不是开发 jBPM 必须的工具,但它是对 jBPM 开发很有帮助的工具,特别是 jBPM 提供了一个 Eclipse 插件用来辅助开发 jBPM 。关于 Eclipse 的安装不赘述了,本文用的版本是: Eclipse3.2 

 

安装jBPM 

jBPM 的下载地址: http://www.jboss.com/products/jbpm/downloads   

l           JBoss jBPM 是 jBPM 的软件包 

l           JBoss jBPM Starters Kit  是一个综合包,它包括了 jBPM 软件包、开发插件、一个配置好了的基于 JBoss 的 jBPM 示例、一些数据库配置文件示例。 

l           JBoss jBPM Process Designer Plugin  是辅助开发 jBPM 的 Eclipse 插件。 

l           JBoss jBPM BPEL Extension jBPM 关于 BPEL 的扩展包 

本指南选择下载: JBoss jBPM Starters Kit 。下载后解压到 D:/jbpm-starters-kit-3.1 ,目录下含有五个子目录: 

l           jbpm   jBPM 的软件包 

l           jbpm-bpel 只含有一个网页 

l           jbpm-db 各种数据库 hibernate 配置文件示例,有些还包含了相应的 jdbc 驱动程序。 

l           jbpm-designer  辅助开发 jBPM 的 Eclipse 插件,具体在 jbpm-gpd-feature 子目录中 

l           jbpm-server  一个已经配置好了的基于 JBoss 的 jBPM 示例 . 

 

感觉下工作流

前面我们说了,在 JBoss jBPM Starters Kit 的 jbpm-server 目录是一个已经配置好的了 jBPM 示例,那么让我们来感觉一下 jBPM 做出的东西吧。 

 

双击 jbpm-server 目录下的 start.bat 文件,启动 JBoss 服务。这时会打开一个 DOS 窗口,启动完成后,日志会不断输出,其中最后一句是“ 13:55:39,937 DEBUG [StaticNotifier] going to wait for (CMD_EXECUTOR, java.lang.Object@1df59bd) ”,这表示 jBPM 在开始工作了,它不断进行轮询。 

打开网页: http://localhost:8080/jbpm/  得到如下画面 

 

这是一个已经用 jBPM 开发好的用户定单流程,具有下单、审核、估价等流程。它所用的数据库是一个内置的数据库。 

以 cookie monster 用户登录,选择“ create new web sale order ”可以创建一个定单。如下图所示,在图左边是填写的定单情况,右边一整个定货流程的示意图,红色框表示流程进行到哪一步了。填写好定单好,选择“ Save and Close Task ”,完成定单提交。 

 

选择右上角的“ Login as another user ”以另外一个用户名 ernie 登录。这时可以看到 ernie 用户的任务列表中多了一项。 

 

点进去后,显示如下画面。这个示例对中文的支持不好,全都显示成了 unicode 码了。不管这什么多,反正知道是这么回事就行了。在 comment 项填写意见,选 OK 按钮,进入到下一步。如果选择 more info needed 按钮,则打回给 cookie monster 用户修改定单。 

 

下面的流程,这里就不再赘述了。在这个很标准的工作流示例中,我们基本可以看到 jBPM 的应用范围还是比较广的。而且从这个示例,我们是看不出有 jBPM 的,也就是说 jBPM 在后台起着作用。 

从这个例子,还看不出 jBPM 的优势。不过,如果在一个流程不确定,经常需要变动的项目中, jBPM 的好处将会显然出来。应用 jBPM 后,改变流程只需改变流程描述文件,这将在后面的内容提到。 

这是一个已做好的示例,接下来我们将仿造这个实例来开发一个请假流程。
 

4 数据库初始化

jBPM 需要数据库支持, jBPM 会把自己的一个初始化数据存储到数据库,同时工作流的数据也是存储到数据库中的。 jBPM 使用 Hibernate 来做为自己的存储层,因此只要是 Hibernate 支持的数据库, jBPM 也就支持。 

 

本文先以 MySQL 为例,然后再以 Oracle 为例,来谈谈 jBPM 的数据库初始化操作。 

 

注:在上面的 JBoss 自带的示例中,并没有设置数据库,那是因为 jBPM 默认使用的是内存数据库 hsqldb  

4.1  MySQL

 

1 、首先安装 MySQL 。 

MySQL 的安装比较简单,网上也有很多文章,本文不再赘述。本指南所用 MySQL 版本为 MySQL 4.1 ( for windows )。再找一个 MySQL 客户端,目的是方便查看数据库中的数据,本文推荐使用 MySQL 网站上免费提供的“ MySQL Query Brower ”,当然你用其他的客户端也行,比如 MySQL-Front 。 

 

2 、建库 

MySQL 中创建一个库,库名: jbpm 

 

3 、生成建表的 SQL 语句并建表 

将 jbpm-starters-kit-3.1.1 下的子目录 jbpm 改名为 jbpm.3 ,否则在执行下面的 ant 命令时会报如 jbpm.3 目录不存在的错误: 

D:/jbpm-starters-kit-3.1.1/jbpm-db/build.xml:361: The following error occurred while executing this line: 

D:/jbpm-starters-kit-3.1.1/jbpm-db/build.xml:68: Basedir D:/jbpm-starters-kit-3.1.1/jbpm.3 does not exist 

 

在 DOS 窗下,进入 D:/jbpm-starters-kit-3.1.1/jbpm-db 目录,执行如下命令: 

ant mysql.scripts 

 

执行成功后,在 D:/jbpm-starters-kit-3.1.1/jbpm-db/build/mysql/scripts 目录里生成了四个 sql 文件,它们做什么用的一看名字就知道了。在 MySQL 客户端中执行“ mysql.create.sql ”脚本,这样将在 jbpm 库中创建一个个的数据表。 

 

 

4.2  Oracle

先安装好 Oracle 服务器。我们公司有现存的 Oracle 服务器,也提供给了我一个属于我自己的用户名,一登录就可以任意在我的库之下创建表了。所以这一步就省了,没有的自个先装好吧。 

访问 Oracle 推荐用“ PLSQL Developer ”。不过要连接 Oracle 还要在本机上装上 Oracle 自己的客户端程序,里面提供了 JDBC 包和一些配置。要连接服务器还得配置一下,我一般都是不用 GUI 而直接改 tnsnames.ora 文件,在我的电脑里此文件的目录地址是: D:/oracle/ora92/network/ADMIN/tnsnames.ora ,内容如下 ( 两面有两个配置了 ) : 

# TNSNAMES.ORA Network Configuration File: E:/oracle/ora92/network/admin/tnsnames.ora 

# Generated by Oracle configuration tools. 

 

WXXRDB_192.168.3.2 = 

  (DESCRIPTION = 

    (ADDRESS_LIST = 

      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.3.2)(PORT = 1521)) 

    ) 

    (CONNECT_DATA = 

      (SID = wxxrDB) 

      (SERVER = DEDICATED) 

    ) 

  ) 

 

WXXRDB_192.168.4.2 = 

  (DESCRIPTION = 

    (ADDRESS_LIST = 

      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.4.2)(PORT = 1521)) 

    ) 

    (CONNECT_DATA = 

      (SID = wxxrDB) 

      (SERVER = DEDICATED) 

    ) 

  ) 

 

前面都是 Oracle 的一些知识,不会的 Google 一下吧。最后配置好后,用 PLSQL Developer 输入你的用户名和密码联接到 Oracle ,就算 OK 了。 

 

参考上面 MySQL 的步骤,基本一样: 

(1)       将 jbpm 改名为 jbpm.3 (或者修改jbpm-db目录下的build.properties文件)

(2)       再执行 ant oracle.scripts 

(3)       用 jbpm-db/build/oracle/scripts 目录的 oracle.create.sql 脚本,在 Oracle 中生成 jBPM 的所有表。在“ PLSQL Developer ”中可以新建一个 Command Windows 窗口然后输入命令: @D:/jbpm-starters-kit-3.1.1/jbpm-db/build/oracle/scripts/oracle.create.sql 

(如果报错,就将报错的地方注释掉)

5  安装jBPM 的Eclipse 开发插件

有个辅助工具开发起来方便一点,只不过现在 jBPM 的开发工具插件功能还不算太强,也就一个“项目创建向导”的功能,让你: 

(1)不用再去配置 classpath 库的引用了 

(2)直接得到了一个 jBPM 的项目初始结构 

其实吧,开发 jBPM 也不需要什么插件工具,在熟练了以后,库引用了项目初始结构都可以手工创建。 

 

插件不用再去下载了, jbpm-starters-kit-3.1.1 包里就有,目录地址如下: D:/jbpm-starters-kit-3.1.1/jbpm-designer/jbpm-gpd-feature/eclipse ,插件的安装方式是链接式还是直接复制式,任选吧。不懂的就去看看《 Eclipse 从入门精通》这本书,在前面章节都有讲到。另外,注明一下 Eclipse 的版本我是用 3.2 ,插件和 Eclispe 版本相关的,要注意了。 

 

如果安装成功,则 Eclipse 首选项里多了一个 JBoss jBPM ,另外我们也需要到这个 jBPM 的首选项里做一些配置工作――指定 jBPM 的安装路径(如下图所示)。这个配置主要是为了找到 jbpm 下的各种 jar 包,好让 Eclipse 设置项目的库引用。本文指向路径是 d:/jbpm-starters-kit-3.1.1/jbpm.3 

 

6  jBPM 的Hello World 

6.1 新建jBPM项目

主菜单“文件->新建->项目”,在弹出的对话框里,有“ Process Project ”项,如下图所示: 


选上好,单击“下一步”,起个名“ myjbpm ”,然后就可以单击“完成”了。然后就生成了如下图所示的一个项目结构:  

这个项目和通常 Eclipse 的项目结构有点不同,不过这是一个现在非常流行的项目结构, src/java 存放源文件, test/java 存放相应的 JUnit 单元测试代码。如果你用 Maven 来编译构建项目,对这种目录结构一定不陌生。 

 

项目创建起了,介绍一下里面的文件吧: 

l           MessageActionHandler ,自动生成的一个 ActionHandler 。不想要可以删掉。 

l           ehcache.xml  cache 的配置文件,里面有很详解的英文说明。没有必要可以不用改它。 

l           hibernate.cfg.xml   jBPM 是用 Hibernate 进行工作流的数据存储的,这个就是 Hibernate 的配置文件。后面我们将讲到如何配置这个文件。 

l           jbpm.cfg.xml   jbpm 本身的配置文件。现在是空的,它用的是缺省配置,你想知道有哪些配置就去看这个文件 D:/jbpm-starters-kit-3.1.1/jbpm.3/src/java.jbpm/org/jbpm/default.jbpm.cfg.xml 

l           log4j.properties  这个是日志 API 包 log4j 的配置文件,用过 log4j 的都知道。 

l           SimpleProcessTest.java  这个是对最重要的流程配置文件的 processdefinition.xml 单元测试代码。这里表扬一点, jBPM 的优良设计使得它的可测试性非常之高,喜欢写 t 单元测试的人有福了。 

l           gpd.xml  用于生成流程图的定义文件。都是一些方框的坐标和长宽 

l           processdefinition.xml  这个是对最重要的流程配置文件,以后写流程要经常和它打交道。 

l           processimage.jpg  一个流程图 


从项目结构来看,我们没有看到 JSP 网页程序,也没有看到 GUI 客户端程序,这些代码都是要我们以后开发中来写的。但本文不准备用 JSP 、 GUI ( Swing 、 SWT )来做示例,而是用 JUnit 代码来做使用 jBPM 客户端来演示。因为 jBPM 实际上是一个后台框架,至于前台是 JSP 还是 Swing 还是无界面的 java.class 都是无关紧要的。在教程里用无界面的 java.class 来做客户端则更方便一些,如果进一步采用 JUnit ,则这样的 java.class 同时还具备了单元测试的功能。以后就是用 JSP 写了 WEB 页面,我们还是可以用这些 JUnit 程序来做单元测试,避免了频繁的鼠标点按 WEB 页面这样的力气活。所以在 jBPM 自带的英文教程里都是一个 JUnit 程序,不仔佃看还真摸不着头脑。 

 

6.2 修改hibernate.cfg.xml

    hibernate.cfg.xml 的默认设置是用 HSQL ,这是一个内存数据库,这种内存数据库用来代替项目实际所用的数据库来做单元测试挺不错的。不过我们这里是要试试用 MySQL 、 Oracle ,那就改一下设置吧。 

注:配置值可参考 D:/jbpm-starters-kit-3.1.1/jbpm-db 对应子目录下的 hibernate.properties 文件。 

1 、 MySQL 的更改如下: 

<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</property> 

<property name="hibernate.connection.username">root</property> 

<property name="hibernate.connection.password">123456</property> 

 

2 、 Oracle 的更改如下: 

<property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property> 

<property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property> 

<property name="hibernate.connection.url">jdbc:oracle:thin:@192.168.123.10:1521:wxxrDB</property> 

<property name="hibernate.connection.username">chengang</property> 

<property name="hibernate.connection.password">chengang</property> 

 

如果你装了 Oracle 的客户端,并且 D:/oracle/ora92/network/ADMIN/tnsnames.ora 里做了如下的设置 

WXXRDB_192.168.123.10 = 

  (DESCRIPTION = 

    (ADDRESS_LIST = 

      (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.123.10)(PORT = 1521)) 

    ) 

    (CONNECT_DATA = 

      (SID = wxxrDB) 

      (SERVER = DEDICATED) 

    ) 

  ) 


则 Oracle 的 hibernate.connection.url 项也可以设为: jdbc:oracle:oci:@WXXRDB_192.168.123.10 

 

6.3 完善库引用

    虽然 jBPM 在创建项目之初给我们设置好了库引用,如下图 

但后面运行时还是报一些 NoClassDefFoundError 异常,如没有对 hibernate3.jar 的引用导致下面的错误 

java.lang.NoClassDefFoundError: org/hibernate/Session 

    at org.jbpm.persistence.db.DbPersistenceServiceFactory.openService(DbPersistenceServiceFactory.java:55) 

    at org.jbpm.svc.Services.getService(Services.java:136) 

    ....... 

 

所以我们要为本文的实例完善库引用。主要是把 MySQL 和 Oracle 的 JDBC 库、以及 Hibernate 的 hibernate3.jar 加入到项目的库引用中。 

(1)       找到缺少的 jar 包 

l           mysql 的 jdbc 包,在 D:/jbpm-starters-kit-3.1.1/jbpm-db/mysql/lib 目录里 

l           oracle 的 jdbc 包, jbmp 中没有包含(可能是没拿到 oracle 授权),我们可以自已去 oracle 网站上下载,或者去 oracle 安装目录 D:/oracle/ora92/jdbc/lib 找 ojdbc14.jar (我们公司用的是 Oracle9i ) 

l           Hibernate3.jar 在目录 D:/jbpm-starters-kit-3.1.1/jbpm.3/lib/hibernate 里。 

(2)       在项目里创建一个 lib 目录,将这三个 jar 复制到 lib 目录。 

(3)       如下图设置三 jar 包的库引用 

 

6.4 开始HellorWorld

这里是一个很简单的请假流程,请假人提交假单给经理审批,经理审批后结束。要说明的是,这个流程并不严谨,比如经理不通过流程应该到哪?不过这并不防碍拿它来做示例,螃蟹还得一个一个的吃。我们先拿这一杆子捅到底的流程做一个最简单的示例,从整体上对 jBPM 工作流开发有概念先。然后我们再慢慢丰富。 

 

1 、定义流程 

流程的定义文件是 processdefinition.xml ,这个是一个关键文件, jBPM 的很大一部份内容都是关于它的。在这里我们把原来自动生成的内容,稍做改动: 

<?xml version="1.0" encoding="GBK"?> 

 

<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="helloworld"

    <!-- 申请 --> 

    <start-state name="request"

       <task

           <controller

              <variable name="name" /> 

              <variable name="day" /> 

              <variable name="note" /> 

           </controller

       </task

       <!-- 流程转向 --> 

       <transition name="to_confirm" to="confirm"

           <action name="requestAction" 

              class ="cn.com.chengang.jbpm.RequestAction"> 

              <reason> 我要请假 </reason

           </action

       </transition

    </start-state

    <!-- 审批 --> 

    <state name="confirm"

       <transition name="to_end" to="end"

           <action name="finishAction" 

              class ="cn.com.chengang.jbpm.ConfirmAction" /> 

       </transition

    </state

    <!-- 结束 --> 

    <end-state name="end" /> 

</process-definition> 


说明: 

流程的名称改成了 helloworld 。(呵呵,也就是这里和 helloworld 有关了) 

<controller> 标签定义了三个数据:姓名、请假天数、说明。 

<transition> 标签定了 request 节点的一个流程转向,这里是转到 confirm 节点。 

<action> 标签定义了流程由一个节点转到另一个节点时,所要执行的动作,动作封装在一个 ActionHandler 类中。比如这里当 request 到 confirm 结点时将执行 RequestAction 类的 execute 方法。 

FinishAction 下面还有一个 <reason> (请假理由),它对应于 FinshAction 的属性 String reason 。 

 

2 、 编写 ActionHandler 

    在上面 processdefinition.xml 里我们定义了两个 ActionHandler : RequestAction 、 ConfirmAction 。其代码如下: 

packagecn.com.chengang.jbpm; 

 

importorg.jbpm.graph.def.ActionHandler; 

importorg.jbpm.graph.exe.ExecutionContext; 

 

publicclass RequestAction implements ActionHandler { 

 

    private static final long serialVersionUID= 1L; 

 

    privateString reason

 

    publicString getReason() { 

       return reason

    } 

 

    public voidsetReason(String reason) { 

       this.reason = reason; 

    } 

 

    public voidexecute(ExecutionContext context) throws Exception { 

       context.getContextInstance().setVariable("note"reason); 

    } 

 

 

说明: ExecutionContext 是一个贯通流程的容器。它是个大宝箱,里面啥玩意都有,后面将更深入的提到。这里的 reasion 就是 processdefinition.xml 中的 ” 我要请假 ” 

 

 

packagecn.com.chengang.jbpm; 

 

importorg.jbpm.graph.def.ActionHandler; 

importorg.jbpm.graph.exe.ExecutionContext; 

publicclass ConfirmAction implements ActionHandler { 

 

    private static final long serialVersionUID= 1L; 

 

    public voidexecute(ExecutionContext context) throws Exception { 

       context.getContextInstance().setVariable("note"" 准假 " ); 

    } 

 

 

OK ,后台的程序就算写完了(前台客户端的程序还没写),下面开始部署。 

 

6.5 部署processdefinition.xml

    我们要把 processdefinition.xml 的流程定义的数据部署到数据库中,因为 jBPM 在正式运行的时候不是去读 processdefinition.xml 文件,而是去读数据库中的流程定义。    这里写了一个个 JUnit 程序来部署 processdefinition.xml ,当然你用普通的 Java Main 也可以。 

packagecom.sample; 

 

importjava.io.FileInputStream; 

importjava.io.FileNotFoundException; 

importjava.io.InputStream; 

 

importjunit.framework.TestCase; 

 

importorg.jbpm.JbpmConfiguration; 

importorg.jbpm.JbpmContext; 

importorg.jbpm.graph.def.ProcessDefinition; 

 

/** 

 * 部署 processdefinition.xml 

  * 

 * @author chengang 

  * 

  */ 

publicclass DeployProcessTest extends TestCase { 

    /** 

     * 在本方法执行完毕后,检查 jbpm_processdefinition 表会多了一条记录 

      * 

     * @throws FileNotFoundException 

      */ 

    public voidtestDeployProcessDefinition() throws FileNotFoundException { 

       // 从 jbpm.cfg.xml 取得 jbpm 的配置 

       JbpmConfiguration config = JbpmConfiguration.getInstance(); 

       // 创建一个 jbpm 容器 

       JbpmContext jbpmContext = config.createJbpmContext(); 

       // 由 processdefinition.xml 生成相对应的流程定义类 ProcessDefinition 

       InputStream is = new FileInputStream("processes/simple/processdefinition.xml"); 

       ProcessDefinition processDefinition = ProcessDefinition.parseXmlInputStream(is); 

       // 利用容器的方法将流程定义数据部署到数据库上 

       jbpmContext.deployProcessDefinition(processDefinition); 

       // 关闭 jbpmContext 

       jbpmContext.close(); 

    } 

 

 

运行此程序,在控制台打印了一些日志,通过。如果出错,仔佃阅读出错信息以判断错误原因,并确定你按照前面两节:“修改 hibernate.cfg.xml ”和“完善库引用”的内容做好了设置。 

 (如果抛出异常09:32:43,791 [main] ERROR ErrorCounter : *** ERROR: line 3:45: expecting "set", found 't'
09:32:43,822 [main] ERROR ErrorCounter : *** ERROR: line 3:45: expecting "set", found 't'
09:32:43,869 [main] ERROR ErrorCounter : *** ERROR: line 3:50: unexpected token: t
09:32:43,931 [main] ERROR SessionFactoryImpl : Error in named query: SchedulerSession.deleteTimersForProcessInstance
org.hibernate.hql.ast.QuerySyntaxError: unexpected token: t near line 3, column 50 [
      
        delete from org.jbpm.scheduler.exe.Timer t
        where t.processInstance = :processInstance
      
    ]
 at org.hibernate.hql.ast.ErrorCounter.throwQueryException(ErrorCounter.java:63)
 at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:215)
 at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:127)
 at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:83)
 at org.hibernate.impl.SessionFactoryImpl.getQuery(SessionFactoryImpl.java:427)
 at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:388)
 at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:291)
 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1005)
 at org.jbpm.persistence.db.DbPersistenceServiceFactory.getSessionFactory(DbPersistenceServiceFactory.java:93)
 at org.jbpm.persistence.db.DbPersistenceService.getSessionFactory(DbPersistenceService.java:74)
 at org.jbpm.persistence.db.DbPersistenceService.getSession(DbPersistenceService.java:78)
 at org.jbpm.persistence.db.DbPersistenceService.getGraphSession(DbPersistenceService.java:201)
 at org.jbpm.JbpmContext.getGraphSession(JbpmContext.java:427)
 at org.jbpm.JbpmContext.deployProcessDefinition(JbpmContext.java:166)
 at com.hanxr.jbpm.action.DeployProcessTest.testDeployProcessDefinition(DeployProcessTest.java:35)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:324)
 at junit.framework.TestCase.runTest(TestCase.java:154)
 at junit.framework.TestCase.runBare(TestCase.java:127)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:118)
 at junit.framework.TestSuite.runTest(TestSuite.java:208)
 at junit.framework.TestSuite.run(TestSuite.java:203)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: line 3:50: unexpected token: t
 at org.hibernate.hql.antlr.HqlBaseParser.deleteStatement(HqlBaseParser.java:242)
 at org.hibernate.hql.antlr.HqlBaseParser.statement(HqlBaseParser.java:139)
 at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:209)
 ... 28 more
09:32:43,947 [main] ERROR SessionFactoryImpl : Error in named query: SchedulerSession.suspendTimersForToken
org.hibernate.hql.ast.QuerySyntaxError: expecting "set", found 't' near line 3, column 45 [
      
        update org.jbpm.scheduler.exe.Timer t
        set t.isSuspended = true
        where t.token = :token
      
    ]
 at org.hibernate.hql.ast.ErrorCounter.throwQueryException(ErrorCounter.java:63)
 at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:215)
 at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:127)
 at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:83)
 at org.hibernate.impl.SessionFactoryImpl.getQuery(SessionFactoryImpl.java:427)
 at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:388)
 at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:291)
 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1005)
 at org.jbpm.persistence.db.DbPersistenceServiceFactory.getSessionFactory(DbPersistenceServiceFactory.java:93)
 at org.jbpm.persistence.db.DbPersistenceService.getSessionFactory(DbPersistenceService.java:74)
 at org.jbpm.persistence.db.DbPersistenceService.getSession(DbPersistenceService.java:78)
 at org.jbpm.persistence.db.DbPersistenceService.getGraphSession(DbPersistenceService.java:201)
 at org.jbpm.JbpmContext.getGraphSession(JbpmContext.java:427)
 at org.jbpm.JbpmContext.deployProcessDefinition(JbpmContext.java:166)
 at com.hanxr.jbpm.action.DeployProcessTest.testDeployProcessDefinition(DeployProcessTest.java:35)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:324)
 at junit.framework.TestCase.runTest(TestCase.java:154)
 at junit.framework.TestCase.runBare(TestCase.java:127)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:118)
 at junit.framework.TestSuite.runTest(TestSuite.java:208)
 at junit.framework.TestSuite.run(TestSuite.java:203)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: line 3:45: expecting "set", found 't'
 at antlr.Parser.match(Parser.java:211)
 at org.hibernate.hql.antlr.HqlBaseParser.setClause(HqlBaseParser.java:337)
 at org.hibernate.hql.antlr.HqlBaseParser.updateStatement(HqlBaseParser.java:183)
 at org.hibernate.hql.antlr.HqlBaseParser.statement(HqlBaseParser.java:133)
 at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:209)
 ... 28 more
09:32:43,947 [main] ERROR SessionFactoryImpl : Error in named query: SchedulerSession.resumeTimersForToken
org.hibernate.hql.ast.QuerySyntaxError: expecting "set", found 't' near line 3, column 45 [
      
        update org.jbpm.scheduler.exe.Timer t
        set t.isSuspended = false
        where t.token = :token
      
    ]
 at org.hibernate.hql.ast.ErrorCounter.throwQueryException(ErrorCounter.java:63)
 at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:215)
 at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:127)
 at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:83)
 at org.hibernate.impl.SessionFactoryImpl.getQuery(SessionFactoryImpl.java:427)
 at org.hibernate.impl.SessionFactoryImpl.checkNamedQueries(SessionFactoryImpl.java:388)
 at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:291)
 at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1005)
 at org.jbpm.persistence.db.DbPersistenceServiceFactory.getSessionFactory(DbPersistenceServiceFactory.java:93)
 at org.jbpm.persistence.db.DbPersistenceService.getSessionFactory(DbPersistenceService.java:74)
 at org.jbpm.persistence.db.DbPersistenceService.getSession(DbPersistenceService.java:78)
 at org.jbpm.persistence.db.DbPersistenceService.getGraphSession(DbPersistenceService.java:201)
 at org.jbpm.JbpmContext.getGraphSession(JbpmContext.java:427)
 at org.jbpm.JbpmContext.deployProcessDefinition(JbpmContext.java:166)
 at com.hanxr.jbpm.action.DeployProcessTest.testDeployProcessDefinition(DeployProcessTest.java:35)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:324)
 at junit.framework.TestCase.runTest(TestCase.java:154)
 at junit.framework.TestCase.runBare(TestCase.java:127)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:118)
 at junit.framework.TestSuite.runTest(TestSuite.java:208)
 at junit.framework.TestSuite.run(TestSuite.java:203)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: line 3:45: expecting "set", found 't'
 at antlr.Parser.match(Parser.java:211)
 at org.hibernate.hql.antlr.HqlBaseParser.setClause(HqlBaseParser.java:337)
 at org.hibernate.hql.antlr.HqlBaseParser.updateStatement(HqlBaseParser.java:183)
 at org.hibernate.hql.antlr.HqlBaseParser.statement(HqlBaseParser.java:133)
 at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:209)
 ... 28 more
则修改JBossIDE-1.5.1.GA-ALL/eclipse/plugins/org.jbpm.core_3.0.8.1/build/jbpm-3.1-beta3.jar/org/jbpm/db/hibernate.queries.hbm.xml文件

  <query name="SchedulerSession.deleteTimersForProcessInstance">
      <![CDATA[
        delete from org.jbpm.scheduler.exe.Timer t
        where t.processInstance = :processInstance
      ]]>
    </query>

    <query name="SchedulerSession.suspendTimersForToken">
      <![CDATA[
        update org.jbpm.scheduler.exe.Timer t
        set t.isSuspended = true
        where t.token = :token
      ]]>
    </query>

    <query name="SchedulerSession.resumeTimersForToken">
      <![CDATA[
        update org.jbpm.scheduler.exe.Timer t
        set t.isSuspended = false
        where t.token = :token
      ]]>
    </query>

改为:
  <query name="SchedulerSession.deleteTimersForProcessInstance">
      <![CDATA[
        delete from org.jbpm.scheduler.exe.Timer 
        where processInstance = :processInstance
      ]]>
    </query>

    <query name="SchedulerSession.suspendTimersForToken">
      <![CDATA[
        update org.jbpm.scheduler.exe.Timer 
        set isSuspended = true
        where token = :token
      ]]>
    </query>

    <query name="SchedulerSession.resumeTimersForToken">
      <![CDATA[
        update org.jbpm.scheduler.exe.Timer 
        set isSuspended = false
        where token = :token
      ]]>
    </query>

)

6.6 从数据库中的查看部署效果

无论是 MySQL 还是 Oracle ,查询 jbpm_processdefinition 表,你会发现多了一条记录,如下图 ( 以 PLSQL Developer 的显示为例 ) 

依次检查各表我们可以发现有如下变化: 




并由此简单判断出各表的作用,表中各字段的作用由字段名也能知晓一二。 

jbpm_processdefinition 

一个流程定义文件对应一条记录,可记录多个流程定义文件,可记录一个流程定义文件的对个版本。 

jbpm_action 

记录 ActionHandler 的对象实例(以名称为标识) 

jbpm_delegation 

记录了 ActionHandler 全类名,以便于用反射方式来加载 

jbpm_envent 

它的 transition 引用了 Jbpm_transition 表的 id ,再看其它字段,估计此表是表示流程转向事件的一个实例,或者是一个各表之间的联接表。 

jbpm_node 

流程结点 

jbpm_transition 

流程的转向定义 

jbpm_variableaccess 

流程中携带的变量。 ACCESS 字段是这些变量的读写权限 

 

jBPM 的客户端开发

有了前面的 HelloWorld 后台流程,我们就要开始客户端程序了。正如前面提到的,本文不写 JSP ,而改采用 JUnit 的形式,输出则用 System.out.println 。举一反三,知道在方法中输入及用 println 输出,在 JSP 和 SWING 等 GUI 界面还不是一样嘛。 

 

这个 JUnit 客户端,我们就借用创建项目时自动生成的 SimpleProcessTest.java 了,改写后如下: 

packagecom.sample; 

 

importjunit.framework.TestCase; 

 

importorg.jbpm.JbpmConfiguration; 

importorg.jbpm.JbpmContext; 

importorg.jbpm.context.exe.ContextInstance; 

importorg.jbpm.graph.def.ProcessDefinition; 

importorg.jbpm.graph.exe.ProcessInstance; 

 

publicclass SimpleProcessTest extends TestCase { 

 

    privateJbpmConfiguration config = JbpmConfiguration.getInstance(); 

    privateJbpmContext ctx = config.createJbpmContext(); 

    // helloworld 对应于 jbpm_processdefinition 表的 name 字段值,也即 processdefinition.xml  name 

    // 这个值得取比较耗时,实际项目里最好和“数据库的 JDBC 连接”一样,让它共享,不要频繁打开关闭。 

    privateProcessDefinition processDefinition = ctx.getGraphSession().findLatestProcessDefinition("helloworld"); 

 

    public voidtestNewRequest() { 

       longid = newRequest(); 

       System.out.println("id=" + id); 

       checkNewRequest(id); 

       confirmRequest(id); 

       checkconfirmRequest(id); 

       ctx.close();// 关闭 jbpm 容器 

    } 

 

    /** 

      * 创建一个请假单 

      * 

      * @return

      */ 

    private longnewRequest() { 

       // 创建一个新流程 

       ProcessInstance pi = processDefinition.createProcessInstance(); 

       // 取得流程的数据环境 

       ContextInstance ci = pi.getContextInstance(); 

       // 创建一张请假单 

       ci.setVariable("name"" 陈刚 www.chengang.com.cn" ); 

       ci.setVariable("day", 2); 

       assertEquals(null, ci.getVariable("note")); 

       // 请假申请结束,转到下一个流程结点 

       pi.signal(); 

       returnpi.getId(); 

    } 

 

    /** 

      * 检查请假单的数据 

      * 

      * @paramid 

      */ 

    private voidcheckNewRequest(long id) { 

       // 从数据库提取原流程 

       ProcessInstance pi = ctx.loadProcessInstance(id); 

       // 取得流程的数据环境 

       ContextInstance ci = pi.getContextInstance(); 

       // 创建一张请假单 

       assertEquals(" 陈刚 www.chengang.com.cn" , ci.getVariable("name")); 

       assertEquals(Integer.valueOf(2), ci.getVariable("day")); 

       assertEquals(" 我要请假 " , ci.getVariable("note")); 

 

       // 当前是结点为 confirm 

       assertEquals(pi.getRootToken().getNode().getName(), "confirm"); 

       // 流程还没结束 

       assertFalse(pi.hasEnded()); 

    } 

 

    /** 

      * 审批陈刚的请假申请 

      * 

      * @paramid 

      */ 

    private voidconfirmRequest(long id) { 

       ProcessInstance pi = ctx.loadProcessInstance(id); 

       ContextInstance ci = pi.getContextInstance(); 

       // 不通过 

       ci.setVariable("note"" 不准请假,继续加班 " ); 

       // 审批结束,到下一个流程结点 

       pi.signal(); 

    } 

 

    private voidcheckConfirmRequest(long id) { 

       ProcessInstance pi = ctx.loadProcessInstance(id); 

       ContextInstance ci = pi.getContextInstance(); 

       // ConfirmAction 类在 signal 后执行,所以覆盖了经理的审批意见 

       assertEquals(" 准假 " , ci.getVariable("note")); 

 

       // 当前是结点为 end 

       assertEquals(pi.getRootToken().getNode().getName(), "end"); 

       // 流程结束了 

       assertTrue(pi.hasEnded()); 

    } 

 

}

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:160125次
    • 积分:2469
    • 等级:
    • 排名:第14923名
    • 原创:61篇
    • 转载:156篇
    • 译文:0篇
    • 评论:22条
    最新评论