springhibernatejpaactivititransactionmanager

Manage Hibernate and Activiti with Common TransactionManager


I want to use a common transaction manager (JpaTransactionManager) for hibernate and activiti, but i can not! And i have read all internet resources for that! Hear is a simple scenario (which does not even use hibernate!!):

  1. Save variables in task
  2. Save variable in task#execution
  3. Complete task

Scenario implementation (in a spring bean method with @Transactional annotation):

@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());
    }
}

It's obvious: if taskService.complete throws error, the whole transaction should be rollbacked, so all saved variables should be rollbacked, and the below test case should be passed:

@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"));
}

But it fails, the variables are committed to DB (not rollbacked).

Spring context (Using org.springframework.orm.jpa.JpaTransactionManager as transaction manager):

<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>

Versions:

What is wrong with my configurations?

P.S.

UPDATE::

By using org.springframework.jdbc.datasource.DataSourceTransactionManager instead of org.springframework.orm.jpa.JpaTransactionManager the test case passed. But now i can not persist JPA entities.


Solution

  • The problem is solved by resolving conflict between MyBatis (JDBC) and Hibernate (JPA):

    jpaVendorAdapter property should be added to entityManagerFactory bean:

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

    So entityManagerFactory bean should be like this:

    <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>
    

    For more details see answer of this question.