1、新建项目


   主菜单“文件->新建->项目”,在弹出的对话框里,选中JBoss jbpm节点下的“ Process Project ”项。输入项目名:JbpmProject,项目建好后结构如图所示:

       

第四课  JBPM简单实例入门_transition

     

    这个项目和通常 Eclipse 的项目结构有点不同,不过  这是一个现在非常流行的项目结构, src/java 存放源文件, test/java 存放相应的 JUnit 单元测试代码。

下面介绍一下各个文件:

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

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

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

l         jbpm.cfg.xml jbpm 本身的配置文件。现在是空的,它用的是缺省配置,你想知道有哪些配置就去看这个文件E:/software/jbpm-starters-kit-3.1.2/jbpm/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


2、修改hibernate.cfg.cml配置文件


hibernate.cfg.xml 的默认设置是用 HSQL ,这是一个内存数据库,这种内存数据库用来代替项目实际所用的数据库来做单元测试挺不错的。

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

一、网上朋友给我mysql与oracle的配置如下:

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

二、我的配置修改

 1、sqlserver

<!-- hibernate dialect -->    
    <property name="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</property>    
 
    <!-- JDBC connection properties (begin) -->    
    <property name="hibernate.connection.driver_class">net.sourceforge.jtds.jdbc.Driver</property>    
    <property name="hibernate.connection.url">jdbc:jtds:sqlserver://localhost:1433/jbpm</property>    
    <property name="hibernate.connection.username">sa</property>    
    <property name="hibernate.connection.password">admin</property>    
<!-- JDBC connection properties (end) -->


3、完善库引用


主要是把你所用的数据库的 JDBC 库引用进来,如果你是下载的jbpm-starters-kit-3.1.2可能还要把 Hibernate 的 hibernate3.jar 加入到项目的库引用中。


4、开始流程


这里是一个很简单的请假流程,请假人提交假单给经理审批,经理审批后结束。

1 、定义流程

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

<?xml version="1.0" encoding="UTF-8"?>   
 
<process-definition    
  xmlns="urn:jbpm.org:jpdl-3.2"   
  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="com.stt.jbpm.RequestAction">   
            <reason>我要请假</reason>   
         </action>   
      </transition>   
   </start-state>   
   <!-- 审批 -->   
   <state name="confirm">   
      <transition name="to_end" to="end">   
         <action name="finishAction" class="com.stt.jbpm.ConfirmAction"/>   
      </transition>   
   </state>   
   <!-- 结束 -->   
   <end-state name="end"></end-state>   
</process-definition>

说明:

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

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

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

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

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


5、编写 ActionHandler


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

package com.stt.jbpm;   
 
import org.jbpm.graph.def.ActionHandler;   
import org.jbpm.graph.exe.ExecutionContext;   
 
public class RequestAction implements ActionHandler {   
    private static final long serialVersionUID = 1L;   
       
    private String reason;   
       
    @Override   
    public void execute(ExecutionContext context) throws Exception {   
       context.getContextInstance().setVariable("note", reason);   
    }   
 
    public String getReason() {   
       return reason;   
    }   
 
    public void setReason(String reason) {   
       this.reason = reason;   
    }   
 
}

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

package com.stt.jbpm;   
 
import org.jbpm.graph.def.ActionHandler;   
import org.jbpm.graph.exe.ExecutionContext;   
 
public class ConfirmAction implements ActionHandler {   
    private static final long serialVersionUID = 1L;   
       
    @Override   
    public void execute(ExecutionContext context) throws Exception {   
       context.getContextInstance().setVariable("note", "准假");   
    }   
 
}


6、部署processdefinition.xml


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

package com.stt.jbpm;   
 
import junit.framework.TestCase;   
 
import org.jbpm.JbpmConfiguration;   
import org.jbpm.JbpmContext;   
import org.jbpm.graph.def.ProcessDefinition;   
 
/**   
 * 流程部署   
 * @author USER   
 *   
 */   
public class DeployProcessTest extends TestCase {   
 
    /**   
     * 本方法执行完毕后,检查jbpm_processdefinition表会多了一条记录   
     * @throws Exception   
     */   
    public voidtestDeployProcess() throws Exception {   
        //从jbpm.cfg.xml取得jbpm的配置   
       JbpmConfiguration config = JbpmConfiguration.getInstance();   
       //创建一个jbpm容器   
       JbpmContext jbpmContext = config.createJbpmContext();   
       //由processdifinition.xml生成相对应的流程定义类ProcessDefinition   
       ProcessDefinition processDefinition = ProcessDefinition.parseXmlResource("simple/processdefinition.xml");   
           
       //利用容器的方法将流程定义数据部署到数据库上   
       jbpmContext.deployProcessDefinition(processDefinition);   
       //关闭jbpmContext   
       jbpmContext.close();   
    }   
}

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

报错:

14:44:50,921 [main] INFO  JpdlXmlReader : process xml information: no swimlane or assignment specified for task '   
<task xmlns="urn:jbpm.org:jpdl-3.2" blocking="false" signalling="true" priority="normal" notify="false">   
          <!-- controller管理task变量 -->   
          <controller config-type="field">   
                <variable name="name" access="read,write"/>   
                <variable name="day" access="read,write"/>   
                <variable name="note" access="read,write"/>   
          </controller>   
     </task>'   
14:44:55,765 [main] ERROR JDBCExceptionReporter : Table not found in statement [select top ? processdef0_.ID_ as ID1_0_, processdef0_.NAME_ as NAME3_0_, processdef0_.DESCRIPTION_ as DESCRIPT4_0_, processdef0_.VERSION_ as VERSION5_0_, processdef0_.ISTERMINATIONIMPLICIT_ as ISTERMIN6_0_, processdef0_.STARTSTATE_ as STARTSTATE7_0_ from JBPM_PROCESSDEFINITION processdef0_ where processdef0_.NAME_=? order by processdef0_.VERSION_ desc]   
14:44:55,765 [main] ERROR GraphSession : org.hibernate.exception.SQLGrammarException: could not execute query

咋回事呢?查看hibernate.cfg.xml配置,原来中午修改的配置忘了保存。重新修改,再此运行,OK搞定。

到底成功了没呢?


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


查询以下各表:

jbpm_processdefinition

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

jbpm_action

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

jbpm_delegation

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

jbpm_envent

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

jbpm_node

流程结点

jbpm_transition

流程的转向定义

jbpm_variableaccess

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

例如:查询 jbpm_processdefinition 表,你会发现多了一条记录,查询结果如下:

字段名                      字段值

ID_                            1

CLASS_                         p

NAME_                          helloworld

DESCRIPTION_                   NULL 

VERSION_                       1 

ISTERMINATIONIMPLICIT_         0  

STARTSTATE_                    1                                                                 

表中各字段的作用由字段名也能知晓一二。其他表的查询下面寄不再一一列出。

select * from jbpm_processdefinition

select * from jbpm_action

select * from jbpm_delegation

select * from jbpm_event

select * from jbpm_node

select * from jbpm_transition

select * from jbpm_variableaccess

可以看到数据库中中文“我要请假”全部为“???”,这中文乱码该如何解决呢?下面我贴一下网上其他朋友的经验:

l         JBPM在Mysql 4.0以下运行有问题,主要是select语句的问题。

l         JBPM 数据库默认的字符段是255个字符,有时需要修改,比如存储文件的时候。JBPM数据库默认的字段类型的定义有时候不一定适合需要,要手工进行修改。

l         对于工作流定义文件-processdifinition.xml的中文问题解决方案如下:

1)       processdefinition.xml的Encoding设定成”GBK“

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

2)       对于MySQL,hibernate相应的hibernate.connection.url设定成:jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=GBK

对于Sqlserver设定为:

<property name="hibernate.connection.url">jdbc:jtds:sqlserver://localhost:1433;SelectMethod=cursor;characterEncoding=GBK;DatabaseName=jbpm</property>

对于Oracle来讲,Hibernate使用的C3P0连接池有问题,可用最新的C3P0代替。

3)       文字在显示的时候要用toGBK转换才能正确显示。

        如果XML定义文件采用UTF8定义,显示时可不用做GBK转换。

 SQLSERVER 2005 中文乱码问题:

本人用的是sqlserver 2005,当保存到数据库中后,表中全部为乱码。按照上面的方法还是无法解决。Hibernate配置文件里里关于解决乱码问题所采用的统一编码方式也尝试过,数据库里依然是“???”,究竟是哪里出了问题?

我想是不是数据库根本就无法保存中文,在数据库乱码字段中的值修改为中文,输完后发现马上变成了“???”,嘿,还真是数据库本身出了问题。

由于在安装sqlserver 2005时帮助那些安装失败,所以只好查看机子上原来安装的sqlserver 2000中的帮助,输入“汉字”里面有一篇:使用 Unicode 数据

Unicode 规格通过采用两个字节编码每个字符使这个问题迎刃而解。转换最通用商业语言的单一规格具有足够多的 2 字节的模式 (65,536)。因为所有的 Unicode 系统均一致地采用同样的位模式来代表所有的字符,所以当从一个系统转到另一个系统时,将不会存在未正确转换字符的问题。通过在整个系统中使用 Unicode 数据类型,可尽量减少字符转换问题。

在 Microsoft SQL Server 中,以下数据类型支持 Unicode 数据:

nchar ,  nvarchar  , ntext

把乱码对应字段类型varchar(4000)改为nvarchar(4000)

再次在乱码字段输入中文,输完后发现正常,“???”没再回来。^_^

趁热打铁,运行程序,o(∩_∩)o…哈哈 ,ok!一切正常!

把原来的建表语句中的varchar全改为nvarchar,重新建。


8、开发客户端测试


本文不写 JSP ,而改采用 JUnit 的形式,输出则用 System.out.println。

package com.stt.jbpm;   
 
import org.jbpm.JbpmConfiguration;   
import org.jbpm.JbpmContext;   
import org.jbpm.context.exe.ContextInstance;   
import org.jbpm.graph.def.ProcessDefinition;   
import org.jbpm.graph.exe.ProcessInstance;   
 
import junit.framework.TestCase;   
 
public class ClientProcessTest extends TestCase {   
    private JbpmConfiguration configgetInstance();   
    private JbpmContext ctx = config.createJbpmContext();   
    //helloworld对应于jbpm_processdefinition表的name字段,也即processdefinition.xml的name   
    //这个值得取比较耗时,实际项目里最好和"数据库的JDBC脸接"一样,让它共享,不要频繁打开关闭。   
    private ProcessDefinition processDefinition = ctx.getGraphSession().findLatestProcessDefinition("helloworld");   
       
       
    /**   
     * 测试方法入口   
     */   
    public void testNewRequest(){   
       long id = newRequest();   
       System.out.println("id="+id);   
       checkNewRequest(id);   
       confirmRequest(id);   
       checkconfirmRequest(id);   
       ctx.close();   //关闭jbpm容器   
    }   
 
    /**   
     * 创建一个请假单   
     * @return  本次请假流程ID   
     */   
    private long newRequest() {   
       //创建一个新流程   
       ProcessInstance pi = processDefinition.createProcessInstance();   
       //取得流程的数据环境   
       ContextInstance ci = pi.getContextInstance();   
       //创建一张请假单   
       ci.setVariable("name", "婷婷");   
       ci.setVariable("day", 2);   
       //请假申请结束,转到下一个流程节点   
       pi.signal();   
       return pi.getId();   
    }   
 
        
    /**   
     * 检测请假单的数据   
     * @param id    
     */   
    private void checkNewRequest(long id) {   
       //从数据库提取数据源   
       ProcessInstance pi = ctx.loadProcessInstance(id);   
       //取得流程的数据环境   
       ContextInstance ci   
       //创建一张请假单   
       assertEquals("婷婷",ci.getVariable("name"));   
       assertEquals(Integer.valueOf(2),ci.getVariable("day"));   
       assertEquals("我要请假",ci.getVariable("note"));   
          
       //当前是结点为confirm   
       assertEquals(pi.getRootToken().getNode().getName(),"confirm");   
       //流程还未结束   
       assertFalse(pi.hasEnded());   
    }   
 
    /**   
     *  审批请假申请   
     * @param id   
     */   
    private void confirmRequest(long id) {   
       //从数据库提取数据源   
       ProcessInstance pi = ctx.loadProcessInstance(id);   
       //取得流程的数据环境   
       ContextInstance ci = pi.getContextInstance();   
          
       //假如,审批不通过   
       ci.setVariable("note", "不准请假");   
       //审批结束,到下一个流程结点   
       pi.signal();   
    }   
 
    /**   
     * 申请者取得最后审批结果   
     * @param id   
     */   
    private void checkconfirmRequest(long id) {   
       //从数据库提取数据源   
       ProcessInstance pi = ctx.loadProcessInstance(id);   
       //取得流程的数据环境   
       ContextInstance ci = pi.getContextInstance();   
          
       //ConfirmAction类在signal后执行,所以覆盖了经理的审批意见(注意这就是为何在此不会报错的原因)   
       assertEquals("准假",ci.getVariable("note"));   
       //流程结束了   
       assertTrue(pi.hasEnded());   
    }   
}

查看表JBPM_VARIABLEINSTANCE中将有三条记录分别是name的值为“婷婷”,day为2,note的值为“准假”。

表JBPM_PROCESSINSTANCE中添加了一条记录可知道该流程的开始日期(START_字段指定)和结束日期(END_字段指定)。

这个简单的入门实例就到此结束。后面将开始一点点进入与业务相关的流程学习开发。