使用 Common TransactionManager 管理 Hibernate 和 Activiti

Manage Hibernate and Activiti with Common TransactionManager

我想为hibernate 和activiti 使用通用的事务管理器(JpaTransactionManager),但我不能!我已经阅读了所有互联网资源!听说是一个简单的场景(连hibernate都不用!!):

  1. 在任务中保存变量
  2. 在任务中保存变量#execution
  3. 完成任务

场景实现(在带有 @Transactional 注释的 spring bean 方法中):

@Component
public class TaskManager {

    @Autowired TaskService taskService;
    @Autowired RuntimeService runtimeService;

    @Transactional
    public void completeTask(CompleteTaskRequest request) {
        Task task = taskService.createTaskQuery().taskId(request.getTaskId()).singleResult();
        if (task == null) {
            throw new ActivitiObjectNotFoundException("No task found");
        }
        taskService.setVariableLocal(task.getId(), "actionDisplayUrl", request.getActionDisplayUrl());
        taskService.setVariableLocal(task.getId(), "actionSummaryUrl", request.getActionSummaryUrl());
        runtimeService.setVariableLocal(task.getExecutionId(), "prevTaskId", task.getId());
        taskService.complete(task.getId());
    }
}

很明显:如果taskService.complete抛出错误,整个事务应该回滚,所以所有保存的变量都应该回滚,下面的测试用例应该通过:

@Test
@Deployment(resources = "org.activiti.test/CompleteTaskTest.bpmn20.xml")
public void testCompleteTaskWithError() {
    Map<String, Object> processVars = new HashMap<>();
    processVars.put("error", true); // Causes throwing error in ScriptTaskListener
    runtimeService.startProcessInstanceByKey("CompleteTaskTest", processVars);
    Task task = taskService.createTaskQuery().taskName("Task 1").singleResult();

    CompleteTaskRequest req = new CompleteTaskRequest();
    req.setTaskId(task.getId());
    req.setActionDisplayUrl("/actions/1234");
    req.setActionSummaryUrl("/actions/1234/summary");
    try {
        taskManager.completeTask(req);
        fail("An error expected!");
    } catch(Exception e) {
    }

    // Check variables rollback
    assertNull(taskService.getVariableLocal(task.getId(),"actionSummaryUrl"));
    assertNull(taskService.getVariableLocal(task.getId(),"actionDisplayUrl"));
    assertNull(runtimeService.getVariableLocal(task.getExecutionId(), "prevTaskId"));
}

但它失败了,变量被提交到数据库(未回滚)。

Spring 上下文(使用 org.springframework.orm.jpa.JpaTransactionManager 作为事务管理器):

<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
   <property name="dataSource" ref="dataSource" />
   <property name="transactionManager" ref="transactionManager" />
   <property name="idGenerator" ref="idGenerator"/>
   <property name="databaseSchemaUpdate" value="true" />
   <property name="jpaEntityManagerFactory" ref="entityManagerFactory" />
   <property name="jpaHandleTransaction" value="true" />
   <property name="jpaCloseEntityManager" value="true" />
   <property name="beans" ref="processEngineBeans" />
   <property name="jobExecutorActivate" value="false" />
   <property name="asyncExecutorEnabled" value="true" />
   <property name="asyncExecutorActivate" value="true" />
</bean>

<aop:config proxy-target-class="true" />
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
   <property name="processEngineConfiguration" ref="processEngineConfiguration" />
</bean>

<bean id="processEngineBeans" class="java.util.HashMap">
   <constructor-arg index="0" type="java.util.Map">
      <map>
      </map>
   </constructor-arg>
</bean>

<bean id="repositoryService" factory-bean="processEngine" factory-method="getRepositoryService" />
<bean id="runtimeService" factory-bean="processEngine" factory-method="getRuntimeService" />
<bean id="taskService" factory-bean="processEngine" factory-method="getTaskService" />
<bean id="historyService" factory-bean="processEngine" factory-method="getHistoryService" />
<bean id="managementService" factory-bean="processEngine" factory-method="getManagementService" />
<bean id="identityService" factory-bean="processEngine" factory-method="getIdentityService" />
<bean id="formService" factory-bean="processEngine" factory-method="getFormService" />
<bean id="idGenerator" class="org.activiti.engine.impl.persistence.StrongUuidGenerator" />

<bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
   <property name="processEngine" ref="processEngine" />
</bean>


<bean id="persistenceUnitManager"
     class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
   <property name="packagesToScan" value="org.activiti.test" />
   <property name="defaultDataSource" ref="dataSource" />
</bean>

<bean id="entityManagerFactory"
     class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
   <property name="persistenceUnitManager" ref="persistenceUnitManager" />
   <property name="persistenceProvider">
      <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
   </property>
   <property name="jpaProperties">
      <props>
         <prop key="hibernate.dialect_resolvers">org.hibernate.engine.jdbc.dialect.internal.DialectResolverSet</prop>
         <prop key="hibernate.hbm2ddl.auto">create</prop>
         <prop key="hibernate.cache.use_second_level_cache">false</prop>
         <prop key="hibernate.cache.use_query_cache">false</prop>
         <prop key="hibernate.show_sql">true</prop>
      </props>
   </property>
</bean>

<bean id="dataSource"
     class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
   <property name="driverClass" value="org.h2.Driver" />
   <property name="url" value="jdbc:h2:mem:activiti;DB_CLOSE_DELAY=1000" />
   <property name="username" value="sa" />
   <property name="password" value="" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
   <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

<!-- bean post-processor for JPA annotations -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />

<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" >
   <property name="proxyTargetClass" value="true" />
</bean>

版本:

我的配置有什么问题?

P.S.

更新::

通过使用 org.springframework.jdbc.datasource.DataSourceTransactionManager 而不是 org.springframework.orm.jpa.JpaTransactionManager 测试用例通过了。但是现在我不能坚持 JPA 实体。

解决MyBatis(JDBC)和Hibernate(JPA)的冲突:

jpaVendorAdapter 属性 应该添加到 entityManagerFactory bean:

<property name="jpaVendorAdapter">
    <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>

所以entityManagerFactory bean应该是这样的:

<bean id="entityManagerFactory"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="persistenceUnitManager" />
    <property name="persistenceProvider">
        <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
    </property>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect_resolvers">org.hibernate.engine.jdbc.dialect.internal.DialectResolverSet</prop>
            <prop key="hibernate.hbm2ddl.auto">create</prop>
            <prop key="hibernate.cache.use_second_level_cache">false</prop>
            <prop key="hibernate.cache.use_query_cache">false</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>

有关详细信息,请参阅 this question 的答案。