在研究JBPM,要应用到项目开发之中,由于系统使用spring作为业务层管理,而JBPM默认使用的hibernate作为持久层管理。我需要将它们集成在一起,幸运的是spring与hibernate本身就有完全兼容的集成应用方案,但是之中还是遇到不少问题。
我这使用的JBPM版本是3.1.2,下载的包是jbpm-starters-kit-3.1.2.zip。
首先要将JBPM持久层部分通过系统自动持久化(使用内存数据库hsqldb除外)。我们先将JBPM部署持久层需要的jar包摘出来,虽然JBPM应用需要不少jar包,但是部署持久层并不是需要很多,清单如下:
jar包 | 路径(BaseDir = jbpm-starters-kit-3.1.2) | 说明 |
hibernate3 | BaseDir \jbpm\lib\hibernate\ | hibernate核心程序,版本3.1 |
spring | 需要下载 | Spring核心程序,这里使用的版本是2.0 |
jbpm-3.1.2 | BaseDir \jbpm\build\ | JBPM核心程序 |
dom4j-1.6.1 | BaseDir \jbpm\lib\dom4j\ | 支持解析xml核心程序 |
spring-modules-jbpm31 | 需要下载 | spring与jbpm集成支持包 |
jboss-j2ee | BaseDir \jbpm\lib\jboss\ | JBOSS支持JBPM需要的java程序 |
cglib-2.1_2jboss | BaseDir \jbpm\lib\jboss\ | 支持JOSS-J2EE和hibernate.tool.hbm2ddl,如果不使用会报错: |
bsh | BaseDir \jbpm\lib\jboss\ | 支持JOSS-J2EE,如果不使用会报错:bsh/EvalError |
asm | BaseDir \jbpm\lib\jboss\ | 支持JOSS-J2EE,如果不使用会报错: org/objectweb/asm/Type |
antlr-2.7.5H3 | BaseDir \jbpm\lib\jboss\ | 支持JOSS-J2EE,如果不使用会报错: antlr/ANTLRException |
ehcache-1.1 | BaseDir \jbpm\lib\hibernate\ | 如果使用hibernate缓存则需要 |
ehcache-1.1 | BaseDir \jbpm\lib\hibernate\ | 如果使用hibernate缓存则需要 |
除了上面的jar之外,还需要支持数据库的一些jar包。下面还需要JBPM提供的一些XML配置文件,具体清单如下:
文件名 |
路径 |
说明 |
default.jbpm.cfg.xml |
jbpm-3.1.2,jar\org\jbpm\ |
jbpm主配置文件 |
ehcache.xml |
BaseDir \jbpm\src\config.files\ |
如果使用hibernate缓存则需要配置 |
hibernate.cfg.xml |
BaseDir \jbpm\src\config.files\ |
???,后面具体说明 |
下面可以开始spring的配置了,要让系统自动持久化JBPM持久层,我们得先连上数据库,在spring中我们使用org.apache.commons.dbcp.BasicDataSource类作为数据源连接(需要commons-dbcp.jar支持)。例如:
... 省略若干
然后我们使用spring集成hibernate来建立数据库会话工厂SessionFactory
classpath*:org/jbpm/**/*.hbm.xml
接下来我们要配置的就是JBPM比较关键的BEAN,即jbpmConfiguration;
配置其实很简单,只要将主配置xml文件放在src目录下,按照上面配置就可以,那么问题出来了,如何让它自动部署呢?
JBPM本身提供jbpmConfiguration.createSchema方法来实现,而可以通过硬编码方式,或者在IOC配置jbpmConfiguration中加入〈property name="createSchema" value="true" /〉实现;
我们使用第一种方式看看效果;我这里已经做好了一个监听ServletContextListener,实现方法如下:
public void contextInitialized(ServletContextEvent sce) { ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); JbpmConfiguration jbpmConfiguration = (JbpmConfiguration)ctx.getBean("jbpmConfiguration"); try{ jbpmConfiguration.createSchema(); }catch(Exception e) { e.printStackTrace(); } }
最终控制台输出为:org.hibernate.HibernateException: hibernate.cfg.xml not found,缺少这个配置文件;看来这种方式启动,这个文件是不可缺了;那么我们在src添上这个文件再运行看看效果;
第二次控制台输出为:org.hibernate.HibernateException: The dialect was not set. Set the property hibernate.dialect. 这是怎么回事?
从网上我也找到很多关于这样的配置,例如网上配置如下:
如果按照建立单独数据源dataSource,还是会出现这样的错误,除非使用hibernate.cfg.xml配置数据源;那么我们要使用单独的数据源部署应该怎么做?
由于是 jbpmConfiguration.createSchema();方法报出的错误,我们先将错误详细信息打印出来:
org.hibernate.HibernateException: The dialect was not set. Set the property hibernate.dialect. at org.hibernate.dialect.Dialect.getDialect(Dialect.java:607) at org.hibernate.dialect.Dialect.getDialect(Dialect.java:629) at org.hibernate.tool.hbm2ddl.SchemaExport.(SchemaExport.java:89) at org.hibernate.tool.hbm2ddl.SchemaExport. (SchemaExport.java:64) at org.jbpm.persistence.db.DbPersistenceServiceFactory.getSchemaExport(DbPersistenceServiceFactory.java:76) at org.jbpm.persistence.db.DbPersistenceServiceFactory.createSchema(DbPersistenceServiceFactory.java:107) at org.jbpm.JbpmConfiguration.createSchema(JbpmConfiguration.java:415)
看见在调用createSchema方法时,在方法体调用getSchemaExport方法中有段代码:
public synchronized SchemaExport getSchemaExport() { if (schemaExport==null) { log.debug("creating schema export"); schemaExport = new SchemaExport(getConfiguration()); } return schemaExport; }
可以看见创建初始化SchemaExport对象,我们先看创建这个对象的参数getConfiguration()方法:
public synchronized Configuration getConfiguration() { if (configuration==null) { String hibernateCfgXmlResource = null; if (JbpmConfiguration.Configs.hasObject("resource.hibernate.cfg.xml")) { // 如果配置里含有resource.hibernate.cfg.xml项,那么读这个配置对应的文件 hibernateCfgXmlResource = JbpmConfiguration.Configs.getString("resource.hibernate.cfg.xml"); } ......省略若干 configuration = HibernateHelper.createConfiguration(hibernateCfgXmlResource, hibernatePropertiesResource); } return configuration; }
这里说读resource.hibernate.cfg.xml里的配置,我们可以打开JbpmConfiguration的配置文件default.jbpm.cfg.xml,看到确实是有这个配置,那么注释掉行吗?答案是不行的,最后在createConfiguration方法里还是会默认加载classpath:hibernate.cfg.xml,这是hibernate默认规定的,如果这个文件也没有,则就报错hibernate.cfg.xml not found;
那么就是说这里初始化SchemaExport对象的环境变量就必须是从hibernate.cfg.xml这里配置的,对应到org.hibernate.tool.hbm2ddl.SchemaExport.89行方法调用getDialect方法出错;我们跟进到源码看见getDialect方法是这么写的:
public static Dialect getDialect() throws HibernateException { String dialectName = Environment.getProperties().getProperty( Environment.DIALECT ); if ( dialectName == null ) throw new HibernateException( "The dialect was not set. Set the property hibernate.dialect." ); .... }
意思是从环境变量属性中获取DIALECT 属性名称dialectName如果是空,则报错。
看来我误会了JBPM例子的意思,使用JbpmConfiguration.createSchema方法的方式创建表结构要求是指定hibernate配置(也就是必须要在hibernate.cfg.xml里指定hibernate.properties配置信息),如果不配置hibernate-properties那么必然会出现这样缺少环境变量或缺少数据源信息的错误;
如果是这样,那么解决这个问题?我们要先知道我们的目的是什么?
就是将hibernate配置的持久层配置信息持久化到指定的数据库。那么JbpmConfiguration.createSchema
方法是不是自由JOSS-JBPM独有的呢?我们还是看源码分析,还是从这个方法入口进去,看见persistenceServiceFactory.createSchema();方法,继续跟进,发现getSchemaExport()方法,而这个方法是关键,即也是错误at org.jbpm.persistence.db.DbPersistenceServiceFactory.createSchema中指定的位置。好的这下我们可以再跟进发现一段代码schemaExport = new SchemaExport(getConfiguration());是它才能够调用create创建脚本方法,而SchemaExport并不是JBPM自身有的。
这好像有些废话,用过hibernate的都知道hibernate本身就有Create-Schema功能,源码在org.hibernate.tool.hbm2ddl包下,配置属性为:
classpath*:org/jbpm/**/*.hbm.xml create
好的为了不再说废话hibernate.hbm2ddl.auto,大家可以自己查阅资料。再次部署测试......等待......终于创建成功了。但是还有个问题是hibernate.hbm2ddl.auto-create是在什么时候创建的呢?它是为什么能成功,而jbpmConfiguration.createSchema()不行?
那我们依然看源码,既然是配在LocalSessionFactoryBean类下,就从这里看起。先思考,持久化肯定是在连接数据源,并生成会话之后才能够创建持久模型阿,又从源码上看这个类除了基本的set、get方法外,就只剩afterPropertiesSet中的buildSessionFactory。在此方法里我们可以发现一条代码:
if (this.hibernateProperties != null) { // Add given Hibernate properties to Configuration. config.addProperties(this.hibernateProperties); }
即添加hibernate配置属性,这就对应了IOC里配置的hibernateProperties项,再看最后的newSessionFactory方法中:
Environment.verifyProperties( properties ); //在这里环境变量已经被注入进来了 Properties copy = new Properties(); copy.putAll( properties ); PropertiesHelper.resolvePlaceHolders( copy ); Settings settings = buildSettings( copy ); // 而最后将hibernateProperties配置,转化为对应的settings类 return new SessionFactoryImpl( this, mapping, settings, getInitializedEventListeners() );
跟进SessionFactoryImpl构造函数,发现在295行中有这么段代码:
if ( settings.isAutoCreateSchema() ) new SchemaExport(cfg, settings).create(false, true); 这不就是自动持久化的判断吗?终于真像大白了,我们再总结下这个问题:
1,采用JBPMConfiguration.createSchema方法,在初始化SchemaExport时用到的环境变量必须是从hibernate.cfg.xml来的,而这个文件如果不指定全hibernate信息,则会报错。
2,如果让createSchema是在创建SessionFactory时,所用到的环境变量可以是从IOC容器里配置的,所以甚至可以不使用hibernate.cfg.xml文件。
有些口水话.....