2008年11月5日星期三

jBPM用户指南-CH07

第七章 持久化 Persistence

在大多数场合,jBPM用来在一段很长的时间内维持流程的执行。在这个上下文中,“一段很长的时间”意思是跨越很多个事务。持久化的主要意图是在等待状态下存储流程执行。因此我们把流程执行想像成一个状态机。在一个事务中,我们要把流程执行状态机从一个状态移动到下一个状态。
一个流程定义可以被表述成3种形式:作为XML,作为一个java对象和作为jBPM数据库中的一条记录。可执行(运行时)的信息和日志信息可以被表述成两种形式:作为一个java对象和作为jBPM数据库的一条记录。



图 7.1. The transformations and different forms

更多关于流程定义和流程归档的XML表示形式,请看第16章jBPM Process Definition Language (JPDL)。
更多关于如何部署一个流程归档到一个数据库的信息,请看16.1.1节,“部署一个流程归档”。

7.1. 持久化 API
7.1.1. 配置框架的相互关系
持久化API是和配置框架暴露在JbpmContext之外的一些简单易用的方法的一个集成。因此持久化API操作能够在一个jBPM上下文块内被调用,如下:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {

// Invoke persistence operations here

} finally {
jbpmContext.close();
}

在下文中,我们假设配置包含一个和这个例子(配置文件src/config.files/jbpm.cfg.xml)相似的持久化服务。
<jbpm-configuration>

<jbpm-context>
<service name='persistence' factory='org.jbpm.persistence.db.DbPersistenceServiceFactory' />
...
</jbpm-context>
...
</jbpm-configuration>

7.1.2. JbpmContext的易用的方法
3中最为常见的持久化操作是:
部署一个流程
启动一个流程的执行
继续一个执行

首先部署一个流程定义。典型地,这一步将在一个流程图形设计器或用部署流程的ant任务直接完成。但这里你可以看到如何用编程的方式完成:

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
ProcessDefinition processDefinition = ...;
jbpmContext.deployProcessDefinition(processDefinition);
} finally {
jbpmContext.close();
}

为了创建一个流程执行,我们需要指定这个被实例化的执行是属于哪个流程定义。指定这个最为通用的方法是引用流程的名字且让jBPM在数据库中找到流程的最新版本:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
String processName = ...;
ProcessInstance processInstance =
jbpmContext.newProcessInstance(processName);
} finally {
jbpmContext.close();
}

为了继续一个流程执行,我们需要从数据库获取流程实例、令牌或任务实例,在POJO jBPM对象调用一些方法,然后再次保存对流程实例所作的更新到数据库中。

JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
long processInstanceId = ...;
ProcessInstance processInstance =
jbpmContext.loadProcessInstance(processInstanceId);
processInstance.signal();
jbpmContext.save(processInstance);
} finally {
jbpmContext.close();
}

注意,如果你在JbpmContext使用xxxForUpdate方法,明确的jbpmContext.save方式将不再需要,因为在关闭jbpmContext期间这一步将自动发生。例如:假设我们想要告知jBPM我们已经完成一个任务实例。需要注意的是,任务实例完成将触发执行继续,因此关联到任务实例的流程实例必须保存。要完成这种需求的最为方便的做法是使用loadTaskInstanceForUpdate方法:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
long taskInstanceId = ...;
TaskInstance taskInstance =
jbpmContext.loadTaskInstanceForUpdate(taskInstanceId);
taskInstance.end();
} finally {
jbpmContext.close();
}

下面一部分是关于jBPM如何管理持久化和使用Hibernate的一个说明,只作为一个背景知识。
JbpmConfiguration维护了一个ServiceFactorys的集合。服务工厂在前面说说的jbpm.cfg.xml配置,并以lazy的方式实例化。

DbPersistenceServiceFactory只有在第一次需要的时候实例化。实例化以后,服务工厂在JbpmConfiguration维护。DbPersistenceServiceFactory管理一个hibernate SessionFactory。但同样的,hibernate session factory当在第一次请求的时候以lazy的方式创建。



Figure 7.2. The persistence related classes

在调用jbpmConfiguration.createJbpmContext()的过程中,只有JbpmContext被创建。那时没有更多持久化相关的初始化操作。JbpmContext管理一个DbPersistenceService,DbPersistenceService是在第一次请求时实例化的。DbPersistenceService管理一个hibernate会话。同样的,一个DbPersistenceService之内的hibernate会话以lazy方式创建。因而,一个hibernate会话将只有在第一个请求持久化的操作被调用的时候打开。

7.1.3. 高级 API 用法
DbPersistenceService维护一个lazy初始化的hibernate会话。所有的数据库访问通过这个hibernate会话来完成。所有由jBPM完成的查询和更新是通过XxxSession形式的类来表现,比如GraphSession,SchedulerSession,LoggingSession,...这些会话类与hibernate查询有关,且底层全部使用相同的hibernate会话。
XxxSession类也是通过JbpmContext才可以访问的。

7.2. 配置持久化服务
7.2.1. Hibernate 会话工厂
默认情况下,DbPersistenceServiceFactory将使用ClassPath根目录下的hibernate.cfg.xml资源文件来创建hibernate会话工厂。需要注意的是hibernate配置文件资源和'jbom.hibernate.cfg.xml'属性相映射,且可以在jbpm.cfg.xml中被定制。以下是默认的配置:

<jbpm-configuration>
...
<!-- configuration resource files pointing to default configuration files in jbpm-{version}.jar -->
<string name='resource.hibernate.cfg.xml'
value='hibernate.cfg.xml' />
<!-- <string name='resource.hibernate.properties'
value='hibernate.properties' /> -->
...
</jbpm-configuration>

当resouce.hibernate.properties属性被指定,这些在那个资源文件中的properties将覆盖在hibernate.cfg.xml文件中的所有properties。hibernate.properties可以被用来处理jbpm方便升级:(hibernate.cfg.xml可以被拷贝而不必重新应用改变),而不是使用修改hibernate.cfg.xml的方式来指向你的数据库。

7.2.2. DbPersistenceServiceFactory
DbPersistenceServiceFactory本身有另外三个配置properties:isTransactionEnabled,sessionFactoryJndiName,dataSourceJndiName。想要在jbpm.cfg.xml中指定这些properties,你需要在factory元素中将服务工厂指定为一个bean。就如:
<jbpm-context>
<service name="persistence">
<factory>
<bean factory="org.jbpm.persistence.db.DbPersistenceServiceFactory">
<field name="isTransactionEnabled"><false /></field>
<field name="sessionFactoryJndiName">
<string value="java:/myHibSessFactJndiName" />
</field>
<field name="dataSourceJndiName">
<string value="java:/myDataSourceJndiName" />
</field>
</bean>
</factory>
</service>
...
</jbpm-context>

isTransactionEnabled: 默认情况下,jBPM将会开始和结束hibernate事务。为了禁用事务和阻止jPBM用hibernate管理事务,可以在上述的例子中将isTransactionEnabled配置为false。更多关于事务的信息,请看7.3节,“Hibernate事务”。
sessionFactoryJndiName: 默认情况下,这个值为null,意为会话工厂不是从JNDI获取的。如果这个值被设置,且需要会话工厂来创建一个hibernate会话,那么会话工厂将会使用提供的JNDI名称来被获取。
dataSourceJndiName: 默认情况下,这个值为null,且创建JDBC连接的工作将委托给hibernate。通过制定一个数据源,jBPM将从数据源获取一个JDBC连接,并会在打开一个新的会话时将连接提供给hibernate。要了解用户提供的JDBC连接,请看7.5节,“User provided stuff”。

7.3. Hibernate事务
默认情况下,jBPM将事务委托给hiberante,并在每个事务模式使用会话。当一个hibernate会话打开的时候,jPBM将开始一个hibernate事务。这将在JbpmContext之上的一个持久化操作初次调用的时候发生。事务将在hibernate会话关闭之前正确提交。提交操作在jbpmContext.close()方法内部完成。

使用jbpmContext.setRollbackOnly()来标记一个要回滚的事务。在那种情况下,事务将在jbpmContext.close()方法内执行会话关闭之前被正确回滚。

想要禁止jBPM调用hibernate API之上的任何事务方法,可以如7.2.2节“The DbPersistenceServiceFactory”所讲的将isTransactionEnabled属性设置为false。

7.4. 管理事务
管理事务最为常见的场景是当你在一个诸如JBoss的JEE应用服务器使用jBPM的时候。最为常见的场景有如下几种:
在你的应用服务器配置数据源
配置hibernate来使用数据源的连接
使用容器管理的事务
在jBPM中禁用事务

7.5. User provided stuff
你可以用编程的方式提供JDBC连接,Hiberante会话和Hiberante会话工厂给JBPM.
当这样的一个资源提供给jBPM,它将使用提供的资源而不是使用原先配置的那一个。
JbpmContext类包含了一些便利的方法来以编程的方式注入资源。比如,提供一个JDBC连接给jBPM可以使用如下代码:
JbpmContext jbpmContext = jbpmConfiguration.createJbpmContext();
try {
Connection connection = ...;
jbpmContext.setConnection(connection);

// invoke one or more persistence operations

} finally {
jbpmContext.close();
}

JbpmContext 类有以下几个便利的方法来以编程的方式提供资源:

JbpmContext.setConnection(Connection);
JbpmContext.setSession(Session);
JbpmContext.setSessionFactory(SessionFactory);

7.6.定制查询
所有jBPM使用的HQL查询都集中在一个配置文件中。那个资源文件在hibernate.cfg.xml配置文件被引用:
<hibernate-configuration>
...
<!-- hql queries and type defs -->
<mapping resource="org/jbpm/db/hibernate.queries.hbm.xml" />
...
</hibernate-configuration>
为了定义一个或多个那样的查询,可以拷贝一份原始文件并将你的新版本防止到CLASSPATH的某个地方,然后在hibernate.cfg.xml更新引用'org/jbpm/db/hibernate.queries.hbm.xml'来指向你的定制版本。
7.7. 数据库兼容性
jBPM运行于任何被hibernate支持的数据库之上。
jBPM的实例配置文件(src/config.files)指定了hypersonic内存数据库。那个数据库在开发期间和测试都相当理想。hypersonic内存数据库将它的所有数据库保存在内存,不放到磁盘上。

7.7.1. 改变jBPM数据库
下面的指示列表是在当你想改变jBPM以使用另一个数据库时要做的相关事情:
把jdbc驱动程序jar包放到CLASSPATH中
更新jBPM使用的hibernate配置
在新的数据库创建数据库表

7.7.2. jBPM 数据库方案表(Schema)
jbpm.db子项目,包含了一些驱动、指示和脚本来帮助你对你选择的数据库有个入门。请参考jbpm.db项目根目录下的readme.html来了解更多信息。

尽管jBPM能够为所有的数据库产生DDL脚本,但这些表并不总是经过优化的。所以你可能需要你的数据库管理员检查生成的DDL,以优化字段类型和使用索引。
在开发中你可能插入如下的hibernate配置:如果你把hibernate配置属性‘hibernate.hbm2ddl.auto’设置为‘create-drop’(比如在hibernate.cfg.xml),那么这些数据库表将在第一次在应用中使用到的时候自动创建到数据库中。当应用关闭,数据库表将被删除。

Schema的产生也可以通过jbpmConfiguration.createSchema()和jbpmConfiguration.dropSchema()方法用编程的方式调用。


7.8. 结合你的Hibernate类
在你的项目中,你可能会用Hibernate来做持久化。将你的持久化类和jBPM持久化类组合是可选的。将你的Hibernate持久化类和jBPM的Hibernate持久化结合有两个主要的好处:

首先,会话、连接和事务管理变得更加容易。通过结合jBPM和你的持久化到一个Hibernate会话工厂,将有【一个】hibernate会话,【一个】JDBC连接来处理你的和jBPM的持久化。所以自动地,当jBPM更新操作和在你自己的域模型的更新操作是在同一个事务当中。这可以排除使用一个事务管理的需求。

其次,这将允许你删除你的持久化对象到流程变量中而不用更多的处理。

集成你的持久化类和jBPM持久化类最简单的方式是通过创建一个中心的hibernate.cfg.xml.你可以把jBPM src/config.flies/hibernate.cfg.xml作为一个出发点并在那里添加引用到你自己的hibernate映射文件。

7.9. 定制jBPM hibernate映射文件
要定制任何jBPM的hibernate映射文件,你可以做如下处理:
从源代码src/java.jbpm/...或从jbpm.jar文件内部拷贝你想要拷贝的jBPM hibernate映射文件,并将这些拷贝放在CLASSPATH中你想要的任何地方。在配置文件hibernate.cfg.xml中更新引用到定制的映射文件。

7.10. 第二层缓存
jBPM使用hibernate的第二层缓存以便一旦流程定义装载后在内存中保存流程定义。流程定义类和集合在 jBPM Hibernate映射文件配置,cache元素的配置如:

<cache usage="nonstrict-read-write"/>

尽管流程定义(可能)不会变动,保存在第二层缓存也是可以的。详见第16章:“Changing deployed process definitions”
第二层缓存是JBoss jBPM实现的一个很重要的方面。如果没有缓存,JBoss jBPM和其他技术实现的BPM引擎相比可能有一系列的不足。

缓存策略被设置为:nonstrict-read-write。在运行时,缓存策略可能被设置为只读。但在那种情况下,你可能需要一组独立的hibernate映射文件来部署一个流程。这也是为什么我们选择nonstrict-read-write的原因。

没有评论: