1

I'm running a straight forward batch job to read a record from DB2 DB, I'm not getting any result in the output or an error even.

Any glance of where I'm missing anything?

Here is the code of the batch.

    @Configuration
    @EnableBatchProcessing
    @AllArgsConstructor
    public class ETLConfig {
        
        private JobBuilderFactory jobBuilderFactory;
        private StepBuilderFactory stepBuilderFactory;
        private EntityManagerFactory emf;
    
        @Bean
        public Step sampleStep(){
    
            return stepBuilderFactory.get("sampleStep")
                    .<String, String>chunk(5)
                    .reader(itemReader())
                    .writer(i -> i.stream().forEach(j -> System.out.println(j)))
                    .build();
        }
    
        @Bean
        public Job sampleJob(){
            return jobBuilderFactory.get("sampleJob")
                    .incrementer(new RunIdIncrementer())
                    .start(sampleStep())
                    .build();
        }
    
    
        @Bean
        public JpaPagingItemReader itemReader(/*@Value("${query}") String query*/){
            return new JpaPagingItemReaderBuilder<>()
                    .name("db2Reader")
                    .entityManagerFactory(emf)
                    .queryString("select * FROM EXPORT.FCR_HDR F WHERE F.FCR_REF = 'R2G0128330'")
                    .pageSize(3)
                    .build();
        }
    
    }

If I run the same query on the DB I'm getting the output of this Reference.

@Component
@AllArgsConstructor
public class NoPersistenceBatchConfigurer extends DefaultBatchConfigurer {


    @Bean
    public DataSource batchDataSource() throws ClassNotFoundException {

        return DataSourceBuilder.create()
                .driverClassName("org.h2.Driver")
                .url("jdbc:h2:mem:testdb")
                .username("sa")
                .password("")
                .build();
    }

    @Override
    public void setDataSource(DataSource batchDataSource) {
        try {
            super.setDataSource(batchDataSource());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

} 
a.ak
  • 659
  • 2
  • 12
  • 26
Elias Khattar
  • 163
  • 2
  • 19
  • Have you tried setting the log level to debug? – Mahmoud Ben Hassine Apr 29 '20 at 08:43
  • I turned it on and I got the below error `com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-601, SQLSTATE=42710, SQLERRMC=INETSOFT.BATCH_JOB_INSTANCE;TABLE, DRIVER=4.26.14` @MahmoudBenHassine – Elias Khattar Apr 29 '20 at 10:25
  • That's better. However, I don't know what this error means, but it looks like it does not find the table `BATCH_JOB_INSTANCE`. Have you created the meta-data tables of Spring Batch in your database before running your job? – Mahmoud Ben Hassine Apr 29 '20 at 10:39
  • The DB2 I have only read access to it so cannot create an table, so maybe the batch is not able to create the table there and thus errors? any possible solution for that?@MahmoudBenHassine – Elias Khattar Apr 29 '20 at 10:42
  • In this case, you need to configure Spring Batch to use another DB where it can create tables (this could be an embedded db) or use a Map based job repository, see duplicate question. – Mahmoud Ben Hassine Apr 29 '20 at 11:20
  • Does this answer your question? [Define an in-memory JobRepository](https://stackoverflow.com/questions/44238232/define-an-in-memory-jobrepository) – Mahmoud Ben Hassine Apr 29 '20 at 11:20
  • Yes, that is a bit helpful but bit confusing to me, so I added H2 DB as embedded and I have in the application.propperties the connection to the DB2 that I need to run the query on , so from this solution I need to extend `DefaultBatchConfigurer` and set the datasource there...but I cannot understand how, is there any clear example on how to do that?@MahmoudBenHassine – Elias Khattar Apr 29 '20 at 18:50
  • The following should help: https://stackoverflow.com/questions/25540502/use-of-multiple-datasources-in-spring-batch – Mahmoud Ben Hassine Apr 29 '20 at 21:24
  • Thank you again, there are a lot of different solutions there which is the best one? or the most working one, trying to find out, all I understood that I need to extend the `DefaultBatchConfigurer` and annotate it as `@Component` and configure the embedded DB to be primary...not that straight forward steps or at least I did not know how so far @MahmoudBenHassine – Elias Khattar Apr 30 '20 at 08:02
  • Hi Mahmoud - still not able to get this sorted , I extended `DefaulBatchConfigurer` and left `setDataSource`as empty , one of the solutions stated like this , but still the batch is looking at the DB2 to create the tables and I also included the H2 db as embedded .. editing my code up as well@MahmoudBenHassine – Elias Khattar May 06 '20 at 06:19
  • `I extended DefaulBatchConfigurer and left setDataSource empty`: you should not leave it empty since you previously said "so I added H2 DB as embedded". You need to pass the H2 datasource in `setDataSource` and not leave it empty. – Mahmoud Ben Hassine May 06 '20 at 07:14
  • I updated the `setDataSource` to look at the H2 DB, so basically now my item reader should look into the DB2 that is in my application.properties? I did a debug and I can see that batch is now creating the tables in the embedded H2 but cannot see that the reader is actually reading DB2, and it is giving me error `java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Fcr is not mapped [select f.fcr_ref from Fcr f WHERE f.fcr_ref = 'R2G0128330']` @MahmoudBenHassine – Elias Khattar May 07 '20 at 07:44
  • That's because your reader is still pointing to h2. You did not share your entity manager factory configuration but that's what you need to make sure it points to DB2 before setting it on the reader. I added an answer, please accept it if it helps. – Mahmoud Ben Hassine May 07 '20 at 09:26

2 Answers2

0

Since you have multiple data sources, you need to:

  • configure the job repository to use the H2 database by providing a custom BatchConfigurer (as you mentioned in the description)
  • configure the reader to read data from DB2. For the JpaPagingItemReader, you need to configure the entityManagerFactory to point to the DB2 datasource.
Mahmoud Ben Hassine
  • 28,519
  • 3
  • 32
  • 50
0

It is necessary to specify a main datasource so that spring batch, more specifically the Job repository, saves the metadata related to the jobs, steps, etc.

Example

import javax.sql.DataSource;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class DatabaseConfig {
    
    @Bean()
    @Primary
    public DataSource datasouce() {
                DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
                dataSourceBuilder.driverClassName("org.h2.Driver");
                dataSourceBuilder.url("jdbc:h2:mem:testdb");
                dataSourceBuilder.username("sa");
                dataSourceBuilder.password("password");
                
                return dataSourceBuilder.build();
         
    }
    
    
}

after, we can use specific datasource of other database .

Example of itemReader

public DataSource as400datasource() {

        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("com.ibm.as400.access.AS400JDBCDriver");
        dataSourceBuilder.url("jdbc:as400://PUB400.COM;naming=system;error=true");
        dataSourceBuilder.username("User");
        dataSourceBuilder.password("pass");
        return dataSourceBuilder.build();

    }

    public JdbcCursorItemReader<Empleado> jdbcCursorItemReader() {
        JdbcCursorItemReader<Empleado> jdbcCursorItemReader = new JdbcCursorItemReader<Empleado>();

        jdbcCursorItemReader.setDataSource(as400datasource());

        jdbcCursorItemReader.setSql("SELECT * FROM EMPLEADOS");

        jdbcCursorItemReader.setRowMapper(new BeanPropertyRowMapper<Empleado>() {
            {
                setMappedClass(Empleado.class);
            }
        });

        return jdbcCursorItemReader;
    }

Example of all job (this is only an example to see results fast).

@Configuration
public class SampleJob {

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Autowired
    private FirstItemWriter firstItemWriter;

    @Bean
    public Job chunkJob() {
        return jobBuilderFactory.get("Chunk Job").incrementer(new RunIdIncrementer()).start(firstChunkStep()).build();
    }

    private Step firstChunkStep() {
        return stepBuilderFactory.get("First Chunk Step").<Empleado, Empleado>chunk(5).reader(jdbcCursorItemReader())
                .writer(firstItemWriter).build();
    }
    
    public DataSource as400datasource() {

        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("com.ibm.as400.access.AS400JDBCDriver");
        dataSourceBuilder.url("jdbc:as400://PUB400.COM;naming=system;error=true");
        dataSourceBuilder.username("User");
        dataSourceBuilder.password("pass");
        return dataSourceBuilder.build();

    }

    public JdbcCursorItemReader<Empleado> jdbcCursorItemReader() {
        JdbcCursorItemReader<Empleado> jdbcCursorItemReader = new JdbcCursorItemReader<Empleado>();

        jdbcCursorItemReader.setDataSource(as400datasource());

        jdbcCursorItemReader.setSql("SELECT * FROM EMPLEADOS");

        jdbcCursorItemReader.setRowMapper(new BeanPropertyRowMapper<Empleado>() {
            {
                setMappedClass(Empleado.class);
            }
        });

        return jdbcCursorItemReader;
    }

}

Finally I show the results in itemWriter to check if it was successful connection

Example of itemWriter

@Component
public class FirstItemWriter implements ItemWriter<Empleado> {

    @Override
    public void write(List<? extends Empleado> items) throws Exception {
        System.out.println("Inside Item Writer");
        for(int i = 0 ; i<items.size() ; i++) {
            Empleado empleado = items.get(i);
            System.out.println("Nombre "+ empleado.getNOMBRE());
        }
    }

}

In this particular case, I connect to a DB2 Iseries/as400 and to connect, I need the jdbc jt400.

Note if you connect to as400 Using the one in the version of java 6 or version 8 that come within the jtopen library, the latest versions give problems of unimplemented methods such as isValid().