JBPM与SPRING事务整合之深度历险 博客分类: 探索&发现 JBPMSpringHibernateBeanXML
------------------此文很早就写了,不知何故在博客上找不到了,所以重新录进来...---------------------
spring modules中包含了spring集成JBPM的机制,在使用的发现其并没有彻底解决两者的事务处理统一的问题,经过一段事件的摸索终于将jpbm与spring完全整合,主要是事务处理的整合,工作流代码与业务代码在一个事务上下文进行;
第一步:首先引入spring-modules-jbpm31.jar,同时将jbpm包含的所有hibernate映射文件解压出来,集成到spring配置文件中,可以使用类路径下的目录形式简化,如下:
- <property name="mappingDirectoryLocations">
- <list>
- <value>classpath:/conf/mapping/jbpm/<!---->value>
- <!---->list>
- <!---->property>
经测试在这种方式在weblogic上不能正常加载,从jar包加载也有问题必须解压到目录;
第二步:配置JPBM的大字段处理类型,这一步估计大家都知道,没什么说的;
- <!---->
- <bean id="jbpmTypes" class="org.springframework.orm.hibernate3.TypeDefinitionBean">
- <property name="typeName" value="string_max" />
- <property name="typeClass" value="org.jbpm.db.hibernate.StringMax" />
- <!---->bean>
- <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
- <property name="lobHandler" ref="oracleLobHandle" />
- <property name="dataSource" ref="dataSource" />
- <property name="typeDefinitions"> 注意这里
- <ref bean="jbpmTypes" />
- <!---->property>
- 。。。。。。
第三步:配置spring modules,通过 springmodules 初始化jbpmConfiguration;
<!-- jBPM configuration--> <bean id="jbpmConfiguration" class="org.springmodules.workflow.jbpm31.LocalJbpmConfigurationFactoryBean"> <property name="sessionFactory" ref="sessionFactory" /> <property name="configuration" value="classpath:/conf/jbpm.cfg.xml" />注意这里 <!--<property name="configuration" value="classpath:/org/jbpm/default.jbpm.cfg.xml" />--> <!--<property name="processDefinitions">--> <!--<list>--> <!--<ref local="demoWorkflow" />--> <!--</list>--> <!--</property>--> <!--<property name="createSchema" value="true"/>--> </bean>
<jbpm-configuration> <jbpm-context> <!--<service name="persistence" factory="org.jbpm.persistence.db.DbPersistenceServiceFactory" />--> <service name="persistence"> <factory> <bean class="org.jbpm.persistence.db.DbPersistenceServiceFactory"> <field name="isTransactionEnabled"> <false /> 注意这里 </field> <field name="isCurrentSessionEnabled"> <true /> 注意这里 </field> </bean> </factory> </service> <service name="message" factory="org.jbpm.msg.db.DbMessageServiceFactory" /> <service name="scheduler" factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory" /> <service name="logging" factory="org.jbpm.logging.db.DbLoggingServiceFactory" /> <service name="authentication" factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory" /> </jbpm-context> <!--<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.parsers" value="org/jbpm/jpdl/par/jbpm.parsers.xml" /> <string name="resource.varmapping" value="org/jbpm/context/exe/jbpm.varmapping.xml" /> <long name="jbpm.msg.wait.timout" value="5000" singleton="true" /> <int name="jbpm.byte.block.size" value="1024" singleton="true" /> <string name="mail.smtp.host" value="localhost" /> <bean name="jbpm.task.instance.factory" class="org.jbpm.taskmgmt.impl.DefaultTaskInstanceFactoryImpl" singleton="true" /> <bean name="jbpm.variable.resolver" class="org.jbpm.jpdl.el.impl.JbpmVariableResolver" singleton="true" /> <bean name="jbpm.mail.address.resolver" class="org.jbpm.identity.mail.IdentityAddressResolver" singleton="true" /> </jbpm-configuration>
第四步:修改JBPM自带的过滤器(web.xml),初始化当前请求线程的JBPM Context时从spring获取我们上面配置的jbpmConfiguration;其自带过滤器是通过JbpmConfiguration.getInstance获取的,不能公用spring事务上下文的hibernate session;
- <filter>
- <filter-name>JbpmContextFilter<!---->filter-name>
- <filter-class>com.**.**.workflow.web.JbpmContextHolder<!---->filter-class> 注意这里
- <!---->filter>
- import com.**.**.core.container.ApplicationContext;
- import org.jbpm.JbpmConfiguration;
- import org.jbpm.JbpmContext;
- import javax.servlet.*;
- import javax.servlet.http.HttpServletRequest;
- import java.io.IOException;
- import java.io.Serializable;
- import java.security.Principal;
- public class JbpmContextHolder implements Filter, Serializable {
- private static final long serialVersionUID = 1L;
- String jbpmConfigurationResource = null;
- String jbpmContextName = null;
- boolean isAuthenticationEnabled = true;
- public void init(FilterConfig filterConfig) throws ServletException {
- // get the jbpm configuration resource
- this.jbpmConfigurationResource = filterConfig.getInitParameter("jbpm.configuration.resource");
- // get the jbpm context to be used from the jbpm configuration
- this.jbpmContextName = filterConfig.getInitParameter("jbpm.context.name");
- if (jbpmContextName == null) {
- jbpmContextName = JbpmContext.DEFAULT_JBPM_CONTEXT_NAME;
- }
- // see if authentication is turned off
- String isAuthenticationEnabledText = filterConfig.getInitParameter("authentication");
- if ((isAuthenticationEnabledText != null)
- && ("disabled".equalsIgnoreCase(isAuthenticationEnabledText))
- ) {
- isAuthenticationEnabled = false;
- }
- }
- public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- String actorId = null;
- // see if we can get the authenticated swimlaneActorId
- if (servletRequest instanceof HttpServletRequest) {
- HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
- Principal userPrincipal = httpServletRequest.getUserPrincipal();
- if (userPrincipal != null) {
- actorId = userPrincipal.getName();
- }
- }
- JbpmContext jbpmContext = getJbpmConfiguration().createJbpmContext(jbpmContextName);
- try {
- if (isAuthenticationEnabled) {
- jbpmContext.setActorId(actorId);
- }
- filterChain.doFilter(servletRequest, servletResponse);
- } finally {
- jbpmContext.close();
- }
- }
- 注意:下面是修改后的方法,从spring获取JbpmConfiguration;
- ApplicationContext是我们对spring的封装,可以改成自己的bean加载方式;
- protected JbpmConfiguration getJbpmConfiguration() {
- return (JbpmConfiguration) ApplicationContext.getInstance().getBizComponent("jbpmConfiguration");
- }
- public void destroy() {
- }
- }
第五步:大功告成
经过上边的修改后,从spring事务过程中调用JBPM方法、或者spring modules的方法时都会直接纳入当前事务,实现一致的提交和回滚;
第六步:后话
由于JBPM本身的设计问题,采用这样的解决方案后对JBPM API的调用必须在事务环境中运行,例如不能直接在struts action调用JBPM API代码;当然有解决的办法,但是需要对JBPM做进一步的修改,小弟为了保持JBPM的纯洁性,只改了JBPM的外围代码,没有动大手术,o(∩_∩)o...
另一个相关的问题就是不能直接在单元测试中获取JBPMContext,需要手工开启事务管理器;
附单元测试代码:
public void testNonTrans() throws Exception { try { assertNull(jbpmConfiguration.getCurrentJbpmContext()); jbpmConfiguration.createJbpmContext(); JbpmContext context = jbpmConfiguration.getCurrentJbpmContext(); assertNotNull(context); System.out.println(help.getDecisionHandler());//有事务环境 try { help2.getProcessInstance(new Long(57424));//无事务环境:必然报错 // org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here assertTrue(false); } catch (Exception e) { assertTrue(true); } assertNotNull(basedao.getCurrentSession());//HibernateBaseDAO关联的hibernatetemplate默认的autocreate为true,所以可以得到session assertFalse(basedao.getCurrentSession() == basedao.getCurrentSession());//不在事务中每次创建新的不同的session assertTrue(context.getSessionFactory() == basedao.getSessionFactory());//SessionFactory公用 try { context.getSessionFactory().getCurrentSession();//无事务环境时从SessionFactory获取session必然报错,同context.getSession() // org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here assertTrue(false); } catch (Exception e) { assertTrue(true); } try { basedao.getSessionFactory().getCurrentSession();//无事务环境时从SessionFactory获取session必然报错 // org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here assertTrue(false); } catch (Exception e) { e.printStackTrace(); assertTrue(true); } assertNotNull(context.getSessionFactory().openSession()); assertNotNull(basedao.getSessionFactory().openSession()); assertFalse(context.getSessionFactory().openSession() == basedao.getSessionFactory().openSession()); assertTrue(true); } catch (Exception e) { e.printStackTrace(); assertFalse(true); //失败 } } public void testTransConfig() throws Exception { TransactionStatus status = beginTransation(); assertNull(jbpmConfiguration.getCurrentJbpmContext()); jbpmConfiguration.createJbpmContext(); JbpmContext context = jbpmConfiguration.getCurrentJbpmContext(); assertNotNull(context); try { System.out.println(help.getDecisionHandler()); System.out.println(help2.getProcessInstance(new Long(57424))); assertNotNull(context.getSession()); assertTrue(context.getSession() == context.getSession());//相同的session assertTrue(basedao.getCurrentSession() == basedao.getCurrentSession());//相同的session assertTrue(context.getSessionFactory() == basedao.getSessionFactory());//相同 assertTrue(context.getSession() == basedao.getCurrentSession());//相同 assertTrue(context.getSessionFactory().getCurrentSession() == basedao.getSessionFactory().getCurrentSession());//从相同的SessionFactory取getCurrentSession是相同的 } catch (Exception e) { e.printStackTrace(); } transactionManager.commit(status); /////////// }