14

I'm trying to configure spring batch inside spring boot project and I want to use it without data source. I've found that ResourcelessTransactionManager is the way to go but I cannot make it work. Problem is I already have 3 another dataSources defined, but I don't want to use any of them in springBatch.

I've checked default implementation DefaultBatchConfigurer and if it is not able to find dataSource it will do exactly what I want. Problem is I've 3 of them and dont want to use any.

Please dont suggest to use hsql or other in memory DB as I dont want that.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Majky
  • 1,943
  • 1
  • 18
  • 30
  • I think what you want is impossible. At least use and in-memory db – Luca Basso Ricci Oct 07 '16 at 09:58
  • According to this http://docs.spring.io/spring-batch/reference/html/configureJob.html#inMemoryRepository If I understand it correctly it should be possible. – Majky Oct 07 '16 at 10:03
  • Then manually configure Spring Batch and don't use auto configuration. Just create your own `BatchConfigurer` which does what you want. Register it as a bean and batch will be configured without a datasource. – M. Deinum Oct 07 '16 at 11:42
  • I've done that and it is still looking for DataSource, finds 3 of them and then it fails. – Majky Oct 07 '16 at 12:55
  • Does my answer [here](http://stackoverflow.com/questions/39359840/spring-boot-batch-resourcelesstransactionmanager-datasourcepropertiesdatasource/39366886#39366886) helps? – Sabir Khan Oct 08 '16 at 14:13

5 Answers5

25

I got around this problem by extending the DefaultBatchConfigurer class so that it ignores any DataSource, as a consequence it will configure a map-based JobRepository.

Example:

@Configuration
@EnableBatchProcessing
public class BatchConfig extends DefaultBatchConfigurer {   

    @Override
    public void setDataSource(DataSource dataSource) {
        //This BatchConfigurer ignores any DataSource
    }
}
Pino
  • 7,468
  • 6
  • 50
  • 69
Nandish
  • 306
  • 3
  • 6
  • 5
    Thank you Nandish, this worked for me. I tried this in Springboot application. I also had to add a class to exclusion list in main class: `@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})` – Faraz Sep 22 '18 at 19:13
10

In my case I persist data to Cassandra. If you are using spring-boot-starter-batch it is expected to provide a DataSource which is not yet implemented but you can trick the configuration like in the following steps:

Step1:

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class SampleSpringBatchApplication{

    public static void main(String[] args) {
        System.setProperty("spring.devtools.restart.enabled", "true");
        SpringApplication.run(SampleSpringBatchApplication.class, args);
    }

}

Step2:

    @Configuration
    @EnableBatchProcessing
    public class SampleBatchJob extends DefaultBatchConfigurer {

        //..

        @Override
        public void setDataSource(DataSource dataSource) {
        }

        //..
    }
RazvanParautiu
  • 2,805
  • 2
  • 18
  • 21
4

If you have more than one DataSource in your configuration (regardless of if you want to use them or not) you need to define your own BatchConfigurer. It's the only way the framework knows what to do in situations like that.

You can read more about the BatchConfigurer in the documentation here: http://docs.spring.io/spring-batch/trunk/apidocs/org/springframework/batch/core/configuration/annotation/BatchConfigurer.html

Michael Minella
  • 20,843
  • 4
  • 55
  • 67
  • 2
    This is the only way I was able to get this scenario to work. To clarify, though, you must *completely implement* your own `BatchConfigurer` class. It's not enough to just extend `DefaultBatchConfigurer`, because that will still look for the `DataSource`. I just copied the relevant snippets out of `DefaultBatchConfigurer`. As long as your class is marked as a `@Component`, it should get favored over the other one. – Mac Apr 13 '17 at 16:44
  • 1
    Another caveat is that I needed to use modular batch configuration, and control the way that my config classes get loaded. I put a very simple demo project on GitHub, explaining the pieces involved: https://github.com/macdaddyaz/spring-batch-inmem – Mac Apr 14 '17 at 14:59
3

You can try excluding the DataSourceAutoConfiguration in @SpringBootApplication. See the sample code below.

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableBatchProcessing
public class SampleBatchApplication {

@Autowired
private JobBuilderFactory jobs;

@Autowired
private StepBuilderFactory steps;

@Bean
protected Tasklet tasklet() {

    return new Tasklet() {
        @Override
        public RepeatStatus execute(StepContribution contribution, ChunkContext context) {
            return RepeatStatus.FINISHED;
        }
    };
}

@Bean
public Job job() throws Exception {
    return this.jobs.get("job").start(step1()).build();
}

@Bean
protected Step step1() throws Exception {
    return this.steps.get("step1").tasklet(tasklet()).build();
}

public static void main(String[] args) throws Exception {
    System.exit(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class, args)));
   }
}

And sample test class

import org.junit.Rule;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.test.rule.OutputCapture;
import static org.assertj.core.api.Assertions.assertThat;
public class SampleBatchApplicationTests {
@Rule
public OutputCapture outputCapture = new OutputCapture();

@Test
public void testDefaultSettings() throws Exception {
    assertThat(SpringApplication.exit(SpringApplication.run(SampleBatchApplication.class))).isEqualTo(0);
    String output = this.outputCapture.toString();
    assertThat(output).contains("completed with the following parameters");
  }
}
abaghel
  • 14,783
  • 2
  • 50
  • 66
  • I have added the sample class and test class. It is working for me. As I don't want to use datasource I am not even using ResourcelessTransactionManager . When I run this it prints like " No datasource was provided...using a Map based JobRepository" and "No TaskExecutor has been set, defaulting to synchronous executor." – abaghel Oct 07 '16 at 10:46
  • Exactly, but I've 3 datasources but dont want to use any of them for batch. Sorry updated my question. – Majky Oct 07 '16 at 10:48
  • @Majky try this answer and this answer together: https://stackoverflow.com/a/42721313/4828463 – Faraz Sep 22 '18 at 19:18
3

We had the similar problem, we were using spring boot JDBC and we did not want to store spring batch tables in the DB, but we still wanted to use spring's transaction management for our DataSource.

We ended up implementing own BatchConfigurer.

@Component
public class TablelessBatchConfigurer implements BatchConfigurer {
    private final PlatformTransactionManager transactionManager;
    private final JobRepository jobRepository;
    private final JobLauncher jobLauncher;
    private final JobExplorer jobExplorer;
    private final DataSource dataSource;

    @Autowired
    public TablelessBatchConfigurer(DataSource dataSource) {
        this.dataSource = dataSource;
        this.transactionManager = new DataSourceTransactionManager(this.dataSource);

        try {
            final MapJobRepositoryFactoryBean jobRepositoryFactory = new MapJobRepositoryFactoryBean(this.transactionManager);
            jobRepositoryFactory.afterPropertiesSet();
            this.jobRepository = jobRepositoryFactory.getObject();

            final MapJobExplorerFactoryBean jobExplorerFactory = new MapJobExplorerFactoryBean(jobRepositoryFactory);
            jobExplorerFactory.afterPropertiesSet();
            this.jobExplorer = jobExplorerFactory.getObject();

            final SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
            simpleJobLauncher.setJobRepository(this.jobRepository);
            simpleJobLauncher.afterPropertiesSet();
            this.jobLauncher = simpleJobLauncher;
        } catch (Exception e) {
            throw new BatchConfigurationException(e);
        }
    }
    // ... override getters
}

and setting up initializer to false

spring.batch.initializer.enabled=false
  • 1
    The `spring.batch.initializer.enabled` is deprecated. Probably the `spring.batch.initialize-schema=never` is replacement. – Edgar Asatryan Dec 18 '19 at 11:46
  • Thanks, @Richard for the above working solution. As I am using Hibernate to save other objects in the database (not job related tables) I used JpaTransactionManager. I changed code as below `this.transactionManager = new JpaTransactionManager(entityManagerFactory)` in this `entityManagerFactory` autowired. – Kishor K Nov 21 '21 at 03:39