3.2 数据库示例
jBPM的特性之一就是在流程等待状态时,拥有把流程的执行持久化到数据库中的能力。下面的例子将向你展示怎样存储一个流程实例到数据库,例子中还会出现上下文。分开的方法被用来创建不同的用户代码,例如,一段代码在web应用中启动一个流程并且持久化执行到数据库,稍后,由一个消息驱动bean从数据库中加载流程实例并且恢复它的执行。
有关jBPM持久化的更多信息可以在“第7章 持久化”找到。
public class HelloWorldDbTest extends TestCase {
 
 static JbpmConfiguration jbpmConfiguration = null;
 
 static {
    // 在“src/config.files”可以找到象下面这样的一个示例配置文件。
    // 典型情况下,配置信息在资源文件“jbpm.cfg.xml”中,但是在这里
    // 我们通过XML字符串传入配置信息。    
    // 首先我们创建一个静态的JbpmConfiguration。一个JbpmConfiguration
    // 可以被系统中所有线程所使用,这也是为什么我们可以把它安全的设置
    // 为静态的原因。
 
    jbpmConfiguration = JbpmConfiguration.parseXmlString(
      "<jbpm-configuration>" +
     
      //jbpm-context 机制分离了jbpm核心引擎和来自于外部环境的服务。
     
      " <jbpm-context>" +
      "    <service name='persistence' " +
      "             factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />" +
      " </jbpm-context>" +
     
      // 同样,jbpm使用的所有资源文件在jbpm.cfg.xml中被提供。
     
      " <string name='resource.hibernate.cfg.xml' " +
      "          value='hibernate.cfg.xml' />" +
      " <string name='resource.business.calendar' " +
      "          value='org/jbpm/calendar/jbpm.business.calendar.properties' />" +
      " <string name='resource.default.modules' " +
      "          value='org/jbpm/graph/def/jbpm.default.modules.properties' />" +
      " <string name='resource.converter' " +
      "          value='org/jbpm/db/hibernate/jbpm.converter.properties' />" +
      " <string name='resource.action.types' " +
      "          value='org/jbpm/graph/action/action.types.xml' />" +
      " <string name='resource.node.types' " +
      "          value='org/jbpm/graph/node/node.types.xml' />" +
      " <string name='resource.varmapping' " +
      "          value='org/jbpm/context/exe/jbpm.varmapping.xml' />" +
      "</jbpm-configuration>"
    );
 }
 
 public void setUp() {
    jbpmConfiguration.createSchema();
 }
 
 public void tearDown() {
    jbpmConfiguration.dropSchema();
 }
 
 public void testSimplePersistence() {
    // 在下面调用的3个方法之间,所有的数据通过数据库被传递。
    // 在这个测试中,这3个方法被依次执行,因为我们想要测试一个
    // 完整的流程情景。但是实际上,这些方法表示了对服务器的不同
    // 请求。    
    // 因为我们以一个干净的空数据库开始,所以我们首先必须部署流程。
    // 事实上,这只需要由流程开发者做一次。
    deployProcessDefinition();
 
    // 假设在一个web应用中当用户提交一个表单时我们起动一个流程
    // 实例(=流程执行)…
    processInstanceIsCreatedWhenUserSubmitsWebappForm();
 
    // 然后,一个异步消息到达时继续执行。
    theProcessInstanceContinuesWhenAnAsyncMessageIsReceived();
 }
 
 public void deployProcessDefinition() {
    // 这个测试展示了一个流程定义以及流程定义的执行。
  // 这个流程定义有3个节点:一个没有命名的开始状态,
 // 一个状态“s”,和一个名称为“end”的结束状态。
    ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
      "<process-definition name='hello world'>" +
      " <start-state name='start'>" +
      "    <transition to='s' />" +
      " </start-state>" +
      " <state name='s'>" +
      "    <transition to='end' />" +
      " </state>" +
      " <end-state name='end' />" +
      "</process-definition>"
    );
 
    // 查找在上面所配置的pojo持久化上下文创建器。
    JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
    try {
      // 部署流程定义到数据库中。
      jbpmContext.deployProcessDefinition(processDefinition);
 
    } finally {
      // 关闭pojo持久化上下文。这包含激发(flush)SQL语句把流程
      // 定义插入到数据库。
      jbpmContext.close();
    }
 }
 
 public void processInstanceIsCreatedWhenUserSubmitsWebappForm() {
    // 本方法中的代码可以被放在struts的actiong中,或JSF管理
    // 的bean中。
 
    // 查找在上面所配置的pojo持久化上下文创建器。
    JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
    try {
 
      GraphSession graphSession = jbpmContext.getGraphSession();
     
      ProcessDefinition processDefinition =
          graphSession.findLatestProcessDefinition("hello world");
   
      // 使用从数据库中获取的流程定义可以创建一个流程定义的执行
      // 就象在hello world例子中那样(该例没有持久化)。
      ProcessInstance processInstance =
          new ProcessInstance(processDefinition);
     
      Token token = processInstance.getRootToken();
      assertEquals("start", token.getNode().getName());
      // 让我们起动流程执行
      token.signal();
      // 现在流程在状态 's'。
      assertEquals("s", token.getNode().getName());
     
      // 现在流程实例processInstance被存储到数据库,
      // 因此流程执行的当前状态也被存储到数据库。
      jbpmContext.save(processInstance);
      // 以后我们可以从数据库再取回流程实例,并且通过提供另外一个
      // 信号来恢复流程执行。
 
    } finally {
      // 关闭pojo持久化上下文。
      jbpmContext.close();
    }
 }
 
 public void theProcessInstanceContinuesWhenAnAsyncMessageIsReceived() {
// 本方法中的代码可以作为消息驱动bean的内容。
 
    // 查找在上面所配置的pojo持久化上下文创建器。
    JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
    try {
 
      GraphSession graphSession = jbpmContext.getGraphSession();
      // 首先,我们需要从数据库中取回流程实例。
      // 有几个可选方法来分辨出我们在这里所要处理的流程实例。
      // 在这个简单的测试中,最容易的方式是查找整个流程实例列表,
      // 这里它应该只会给我们一个结果。
 
      // 首先,让我们查找流程定义。
     
      ProcessDefinition processDefinition =
          graphSession.findLatestProcessDefinition("hello world");
 
      // 现在我们搜索这个流程定义的所有流程实例。
      List processInstances =
          graphSession.findProcessInstances(processDefinition.getId());
     
      // 因为我们知道在这个单元测试中只有一个执行。
      // 在实际情况中, 可以从所到达的信息内容中提取processInstanceId
      // 或者由用户来做选择。
      ProcessInstance processInstance =
          (ProcessInstance) processInstances.get(0);
     
      // 现在我们可以继续执行。注意:processInstance 将委托信号
      // 到主执行路径(=根令牌)。
      processInstance.signal();
 
      // 在这个信号之后,我们知道流程执行应该到达了结束状态。
      assertTrue(processInstance.hasEnded());
     
      // 现在我们可以更新数据库中的执行状态。
      jbpmContext.save(processInstance);
 
    } finally {
      // 关闭pojo持久化上下文。
      jbpmContext.close();
    }
 }
}