5

I am trying to configure XA/distributed transactions for a spring batch / spring cloud task application configured with spring boot.

I have added the following dependency hoping to rely on spring boot auto configuration:

compile("org.springframework.boot:spring-boot-starter-jta-atomikos")

However the following two classes cause two transaction managers to be configured:

  • org.springframework.cloud.task.configuration.SimpleTaskConfiguration

  • org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration

See following message:

2016-07-18 21:46:19.952  INFO 18995 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'transactionManager' with a different definition: replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration; factoryMethodName=transactionManager; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/batch/core/configuration/annotation/SimpleBatchConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.cloud.task.configuration.SimpleTaskConfiguration; factoryMethodName=transactionManager; initMethodName=null; destroyMethodName=(inferred); defined in org.springframework.cloud.task.configuration.SimpleTaskConfiguration]

and then because a PlatformTransactionManager named transactionManager is configured, my atomikos auto-configuration is not picked up:

   AtomikosJtaConfiguration did not match
      - @ConditionalOnClass classes found: org.springframework.transaction.jta.JtaTransactionManager,com.atomikos.icatch.jta.UserTransactionManager (OnClassCondition)
      - @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) found the following [transactionManager] (OnBeanCondition)

Can someone please help me prevent this unduly forcing of the transactionManager beans caused by the two classes above?

balteo
  • 23,602
  • 63
  • 219
  • 412
  • Can you provide a runnable example (build.gradle and an application class) that reproduces your issue? Because, like with your previous question, without it all we can do is throw our guesses and assumptions at you and then in the end do nothing. – Miloš Milivojević Jul 21 '16 at 10:56
  • I am trying to put together a sample app that reproduce this. Bear with me. – balteo Jul 21 '16 at 12:19
  • Cool, thanks, that should make it easy to troubleshoot – Miloš Milivojević Jul 21 '16 at 13:23
  • Here it is: https://github.com/balteo/atomikosIssue The only requirement is Docker/Docker-compose and Java. – balteo Jul 25 '16 at 13:34

2 Answers2

2

I had the same issue and my solution was to implement BatchConfigurer (keeping @EnableBatchProcessing) and to add atomikos beans manually.

JobConfig:

@Configuration
@EnableBatchProcessing
public class JobConfig implements BatchConfigurer {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private JtaTransactionManager jtaTransactionManager;

    // ... skipping some code 

    @Override
    public JobRepository getJobRepository() throws Exception {
        JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
        factory.setDataSource(dataSource);
        factory.setTransactionManager(jtaTransactionManager);
        return factory.getObject();
    }

    @Override
    public PlatformTransactionManager getTransactionManager() throws Exception {
        return jtaTransactionManager;
    }

    @Override
    public JobLauncher getJobLauncher() throws Exception {
        SimpleJobLauncher launcher = new SimpleJobLauncher();
        launcher.setJobRepository(getJobRepository());
        launcher.setTaskExecutor(new SimpleAsyncTaskExecutor());
        return launcher;
    }

    @Override
    public JobExplorer getJobExplorer() throws Exception {
        JobExplorerFactoryBean jobExplorerFactoryBean = new JobExplorerFactoryBean();
        jobExplorerFactoryBean.setDataSource(dataSource);
        jobExplorerFactoryBean.afterPropertiesSet();
        return jobExplorerFactoryBean.getObject();
    } 

AtomikosConfig:

@Configuration
public class AtomikosConfig extends AbstractJtaPlatform {

    @Bean(initMethod = "init", destroyMethod = "close")
    @DependsOn("atomikosUserTransactionService")
    public UserTransactionManager atomikosTransactionManager() {
            UserTransactionManager manager = new UserTransactionManager();
            manager.setForceShutdown(false);
            manager.setStartupTransactionService(false);
            return manager;
    }

    @Bean(initMethod = "init", destroyMethod = "shutdownForce")
    public UserTransactionServiceImp atomikosUserTransactionService() {
            Properties properties = new Properties();
            return new UserTransactionServiceImp(properties);
    }

    @Bean
    public UserTransactionImp atomikosUserTransaction() throws SystemException {
            UserTransactionImp transaction = new UserTransactionImp();
            transaction.setTransactionTimeout(300);
            return transaction;
    }

    @Primary
    @Bean
    public JtaTransactionManager jtaTransactionManager() throws Exception {
            JtaTransactionManager manager = new JtaTransactionManager();
            manager.setTransactionManager(atomikosTransactionManager());
            manager.setUserTransaction(atomikosUserTransaction());
            manager.setAllowCustomIsolationLevels(true);
            return manager;
    }

    @Bean
    public ActiveMQXAConnectionFactory xaFactory() {
            ActiveMQXAConnectionFactory factory = new ActiveMQXAConnectionFactory();
            factory.setBrokerURL("tcp://localhost:61616");
            factory.setUserName("admin");
            factory.setPassword("admin");
            //factory.setTrustAllPackages(true);
            factory.setTransactedIndividualAck(true);
            return factory;
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    public AtomikosConnectionFactoryBean connectionFactory() {
            AtomikosConnectionFactoryBean factoryBean = new AtomikosConnectionFactoryBean();
            factoryBean.setUniqueResourceName("amq1");
            factoryBean.setXaConnectionFactory(xaFactory());
            factoryBean.setMaxPoolSize(10);
            return factoryBean;
    }

    @Bean
    public AtomikosJtaPlatform springJtaPlatformAdapter() throws Exception {
            AtomikosJtaPlatform platform = new AtomikosJtaPlatform();
            platform.setJtaTransactionManager(jtaTransactionManager());
            platform.setTransactionManager(atomikosTransactionManager());
            platform.setUserTransaction(atomikosUserTransaction());
            return platform;
    }

    @Override
    protected TransactionManager locateTransactionManager() {
            return atomikosTransactionManager();
    }

    @Override
    protected UserTransaction locateUserTransaction() {
            return atomikosTransactionManager();
    }
rhorvath
  • 3,525
  • 2
  • 23
  • 30
1

After looking at your example, what I can tell you is this - there is no way to make auto-configuration work - even if you disable the auto configuration for transaction management, which you did try, task and batch auto-configurations (triggered by @EnableBatchProcessing and @EnableTask) would still register their own transaction managers and thus stop Atomikos Configuration from being triggered. The reason for this is because @EnableBatchProcessing includes BatchConfigurationSelector configuration class, which in turn includes either SimpleBatchConfiguration or ModularBatchConfiguration and both of them will always register a transaction manager - there's no conditional annotations on either of the bean definitions. @EnableTask does a very similar thing, only with SimpleTaskConfiguration.

So the only way out of this that I can see is for you to create batch and task configurations completely manually.

As for how to manually configure batch and tasks, I would recommend looking at SimpleTaskConfiguration and AbstractBatchConfiguration - you can see there all the beans that you'll need to register.

Alternatively, you can see a batch example on this Java Code Geeks page, you should just translate the XML configuration to Java config.

Miloš Milivojević
  • 5,219
  • 3
  • 26
  • 39
  • Is this really the only way? If that is the case, can you provide instructions or pointers to documentation as to how to create batch and task configurations? – balteo Jul 26 '16 at 11:35
  • Yeah, I added the links to my answer; basically, just refer to the SimpleTaskConfiguration and AbstractBatchConfiguration classes to see what beans you'll need to define manually. – Miloš Milivojević Jul 26 '16 at 13:28
  • I have a similar problem - unfortunately the links are not working anymore. Has anyone a working example that can be shared? Would be greatly appreciated. – Manu Apr 13 '23 at 07:49