Bruno Bruno - 24 days ago 8
Java Question

Camunda Spring Transaction Integration not working

I'm using Camunda 7.3, Spring 4.2.4 and Hibernate 4.3.8 and I'm trying to use them with the same transaction as explained in Camunda Documentation. The transaction works ok with Hibernate operations but not with Camunda operations, if a transaction rollback occurs just the hibernate operations are reverted.

@Configuration
public class CamundaConfiguration {

// Variables with connection Data

public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setPersistenceUnitName("PostgreSQL");
bean.setDataSource(dataSource());
bean.getJpaPropertyMap().put("hibernate.dialect", "org.hibernate.dialect.PostgreSQL82Dialect");
bean.getJpaPropertyMap().put("hibernate.ejb.naming_strategy", NamingStrategyLowerCase.class.getCanonicalName());
bean.getJpaPropertyMap().put("hibernate.jdbc.batch_size", 0);
bean.getJpaPropertyMap().put("hibernate.cache.use_second_level_cache", true);
bean.getJpaPropertyMap().put("hibernate.cache.use_query_cache", true);
bean.getJpaPropertyMap().put("javax.persistence.sharedCache.mode", SharedCacheMode.ALL);
bean.getJpaPropertyMap().put("hibernate.cache.default_cache_concurrency_strategy", "read-write");
bean.getJpaPropertyMap().put("javax.persistence.validation.factory", validator);
bean.getJpaPropertyMap().put("hibernate.cache.region.factory_class", SingletonEhCacheRegionFactory.class.getCanonicalName());
bean.setPersistenceProviderClass(org.hibernate.jpa.HibernatePersistenceProvider.class);
bean.setPackagesToScan("br.com.model");
return bean;
}

@Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager bean = new JpaTransactionManager(entityManagerFactory());
bean.getJpaPropertyMap().put("org.hibernate.flushMode", FlushMode.AUTO);
bean.setDataSource(dataSource);
bean.setPersistenceUnitName("PostgreSQL");
return bean;
}

@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClass);
config.setJdbcUrl(jdbcUrl);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(50);
config.setConnectionTestQuery("select 1");

HikariDataSource bean = new HikariDataSource(config);
return new LazyConnectionDataSourceProxy(bean);
}

@Bean
public ManagedProcessEngineFactoryBean processEngine() {
ManagedProcessEngineFactoryBean processEngineFactoryBean = new ManagedProcessEngineFactoryBean();
processEngineFactoryBean.setProcessEngineConfiguration(processEngineConfiguration());
return processEngineFactoryBean;
}

@Bean
public SpringProcessEngineConfiguration processEngineConfiguration() {
SpringProcessEngineConfiguration processEngineConfiguration = new SpringProcessEngineConfiguration();
processEngineConfiguration.setDataSource(dataSource());
processEngineConfiguration.setTransactionManager(transactionManager());
processEngineConfiguration.setJobExecutorActivate(true);
processEngineConfiguration.setDatabaseSchemaUpdate(ProcessEngineConfigurationImpl.DB_SCHEMA_UPDATE_TRUE);
return processEngineConfiguration;
}

@Bean
public TaskService taskService() throws Exception {
return processEngine().getObject().getTaskService();
}
}


The dataSource and transactionManager is the same used by Spring and Hibernate.

@Service
public class TaskManager {
@Inject
private TaskService taskService;

@Transactional
public void completeTask(String taskId, final Map<String, Object> variables) {

org.camunda.bpm.engine.task.Task camundaTask = taskService.createTaskQuery().taskId(taskId).singleResult();
taskService.complete(camundaTask.getId(), variables);

// Hibernate Operations

throw new RuntimeException("Exception test");
}
}


When executed the code above a rollback will occur and the 'Hibernate Operations' are rollbacked but the operations executed in taskService.complete are not.

I already debugged the Camunda code and everything seems ok, I found a SpringTransactionInterceptor and the commands are executed inside a
TransactionTemplate.execute()
and at this point the transaction is active.

Answer

After studying about transactions, Jpa and Spring, I found out the problem was jpaDialect is not configured, it's responsible to synchronize JDBC and JTA transactions.

The dialect object can be used to retrieve the underlying JDBC connection and thus allows for exposing JPA transactions as JDBC transactions.

I included the following code into configuration and now it's working:

@Configuration
public class CamundaConfiguration {
    ....

    @Bean
    public JpaDialect jpaDialect() {
        return new org.springframework.orm.jpa.vendor.HibernateJpaDialect();
    }

    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
        bean.setJpaDialect(jpaDialect());
        bean.setJpaVendorAdapter(new org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter());
        ...
        return bean;
    }

    @Bean
    public JpaTransactionManager transactionManager() {
        JpaTransactionManager bean = new JpaTransactionManager(entityManagerFactory());
        bean.setJpaDialect(jpaDialect());
        ...
        return bean;
    }

    ...
}