web-dev-qa-db-de.com

Wie kann ich separate Datenquellen für Spring-Batch-Daten und Geschäftsdaten in Java konfigurieren? Soll ich es überhaupt tun?

Mein Hauptjob liest nur Operationen und der andere schreibt etwas, aber MyISAM engine ignoriert Transaktionen, daher würde ich nicht unbedingt die Transaktionsunterstützung benötigen. Wie kann ich Spring Batch so konfigurieren, dass er eine eigene Datenquelle für die JobRepository hat, getrennt von der, die die Geschäftsdaten enthält? Die ersten Datenquellenkonfigurationen werden wie folgt durchgeführt: 

@Configuration
public class StandaloneInfrastructureConfiguration {

    @Autowired
    Environment env;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
      LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
      em.setDataSource(dataSource());
      em.setPackagesToScan(new String[] { "org.podcastpedia.batch.*" });

      JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
      em.setJpaVendorAdapter(vendorAdapter);
      em.setJpaProperties(additionalJpaProperties());

      return em;
    }

    Properties additionalJpaProperties() {
          Properties properties = new Properties();
          properties.setProperty("hibernate.hbm2ddl.auto", "none");
          properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
          properties.setProperty("hibernate.show_sql", "true");

          return properties;
    }

    @Bean
    public DataSource dataSource(){

       return DataSourceBuilder.create()
                .url(env.getProperty("db.url"))
                .driverClassName(env.getProperty("db.driver"))
                .username(env.getProperty("db.username"))
                .password(env.getProperty("db.password"))
                .build();          
    }

    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
      JpaTransactionManager transactionManager = new JpaTransactionManager();
      transactionManager.setEntityManagerFactory(emf);

      return transactionManager;
    }
}

und dann wird es in die Konfigurationsklasse von Job importiert, wo die Annotation @EnableBatchProcessing sie automatisch verwendet. Mein erster Gedanke war, zu versuchen, die Konfigurationsklasse zu setzen, die DefaultBatchConfigurer zu erweitern, aber dann bekomme ich eine 

BeanCurrentlyInCreationException (org.springframework.beans.factory.BeanCurrentlyInCreationException: Fehler beim Erstellen einer Bean mit dem Namen jobBuilders: Angeforderte Bean befindet sich derzeit in der Erstellung: Gibt es eine nicht auflösbare Zirkelreferenz?)

@Configuration
@EnableBatchProcessing
@Import({StandaloneInfrastructureConfiguration.class, NotifySubscribersServicesConfiguration.class})
public class NotifySubscribersJobConfiguration extends DefaultBatchConfigurer {

    @Autowired
    private JobBuilderFactory jobBuilders;

    @Autowired
    private StepBuilderFactory stepBuilders;

    @Autowired
    private DataSource dataSource;

    @Autowired
    Environment env;

    @Override
    @Autowired
    public void setDataSource(javax.sql.DataSource dataSource) {
        super.setDataSource(batchDataSource());
    }

    private DataSource batchDataSource(){          
       return DataSourceBuilder.create()
                .url(env.getProperty("batchdb.url"))
                .driverClassName(env.getProperty("batchdb.driver"))
                .username(env.getProperty("batchdb.username"))
                .password(env.getProperty("batchdb.password"))
                .build();          
    } 

    @Bean
    public ItemReader<User> notifySubscribersReader(){

        JdbcCursorItemReader<User> reader = new JdbcCursorItemReader<User>();
        String sql = "select * from users where is_email_subscriber is not null";

        reader.setSql(sql);
        reader.setDataSource(dataSource);
        reader.setRowMapper(rowMapper());       

        return reader;
    }
........
}   

Alle Gedanken sind mehr als willkommen. Das Projekt ist auf GitHub verfügbar - https://github.com/podcastpedia/podcastpedia-batch

Vielen Dank.

19
amacoder

Ok, das ist seltsam, aber es funktioniert. Das Verschieben der Datenquellen in eine eigene Konfigurationsklasse funktioniert einwandfrei und man kann sich automatisch anmelden.

Das Beispiel ist eine Version mit mehreren Datenquellen von Spring Batch Service Example :

DataSourceConfiguration:

public class DataSourceConfiguration {

    @Value("classpath:schema-mysql.sql")
    private Resource schemaScript;

    @Bean
    @Primary
    public DataSource hsqldbDataSource() throws SQLException {
        final SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriver(new org.hsqldb.jdbcDriver());
        dataSource.setUrl("jdbc:hsqldb:mem:mydb");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(final DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    @Bean
    public DataSource mysqlDataSource() throws SQLException {
        final SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
        dataSource.setDriver(new com.mysql.jdbc.Driver());
        dataSource.setUrl("jdbc:mysql://localhost/spring_batch_example");
        dataSource.setUsername("test");
        dataSource.setPassword("test");
        DatabasePopulatorUtils.execute(databasePopulator(), dataSource);
        return dataSource;
    }

    @Bean
    public JdbcTemplate mysqlJdbcTemplate(@Qualifier("mysqlDataSource") final DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    private DatabasePopulator databasePopulator() {
        final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.addScript(schemaScript);
        return populator;
    }
}

BatchConfiguration:

@Configuration
@EnableBatchProcessing
@Import({ DataSourceConfiguration.class, MBeanExporterConfig.class })
public class BatchConfiguration {

    @Autowired
    private JobBuilderFactory jobs;

    @Autowired
    private StepBuilderFactory steps;

    @Bean
    public ItemReader<Person> reader() {
        final FlatFileItemReader<Person> reader = new FlatFileItemReader<Person>();
        reader.setResource(new ClassPathResource("sample-data.csv"));
        reader.setLineMapper(new DefaultLineMapper<Person>() {
            {
                setLineTokenizer(new DelimitedLineTokenizer() {
                    {
                        setNames(new String[] { "firstName", "lastName" });
                    }
                });
                setFieldSetMapper(new BeanWrapperFieldSetMapper<Person>() {
                    {
                        setTargetType(Person.class);
                    }
                });
            }
        });
        return reader;
    }

    @Bean
    public ItemProcessor<Person, Person> processor() {
        return new PersonItemProcessor();
    }

    @Bean
    public ItemWriter<Person> writer(@Qualifier("mysqlDataSource") final DataSource dataSource) {
        final JdbcBatchItemWriter<Person> writer = new JdbcBatchItemWriter<Person>();
        writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<Person>());
        writer.setSql("INSERT INTO people (first_name, last_name) VALUES (:firstName, :lastName)");
        writer.setDataSource(dataSource);
        return writer;
    }

    @Bean
    public Job importUserJob(final Step s1) {
        return jobs.get("importUserJob").incrementer(new RunIdIncrementer()).flow(s1).end().build();
    }

    @Bean
    public Step step1(final ItemReader<Person> reader,
            final ItemWriter<Person> writer, final ItemProcessor<Person, Person> processor) {
        return steps.get("step1")
                .<Person, Person> chunk(1)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .build();
    }
}
15
Frozen

Ich habe meine Datenquellen in einer separaten Konfigurationsklasse. In der Batch-Konfiguration erweitern wir den DefaultBatchConfigurer und überschreiben die setDataSource -Methode, indem wir die spezifische Datenbank übergeben, die mit Spring Batch mit einem @Qualifier verwendet werden soll. Ich konnte dies nicht mit der Konstrukterversion erreichen, aber die Setter-Methode funktionierte für mich.

Meine Leser, Verarbeiter und Verfasser sind zusammen mit den Schritten in ihren eigenen Klassen untergebracht.

Hierbei wird Spring Boot 1.1.8 & Spring Batch 3.0.1 verwendet.Anmerkung:Wir hatten ein anderes Setup für ein Projekt, das Spring Boot 1.1.5 verwendet, das bei der neueren Version nicht gleich funktionierte.

package org.sample.config.jdbc;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;

/**
 * The Class DataSourceConfiguration.
 *
 */
@Configuration
public class DataSourceConfig {

    private final static Logger log = LoggerFactory.getLogger(DataSourceConfig.class);

    @Autowired private Environment env;

    /**
     * Siphon data source.
     *
     * @return the data source
     */
    @Bean(name = "mainDataSource")
    @Primary
    public DataSource mainDataSource() {

        final String user = this.env.getProperty("db.main.username");
        final String password = this.env.getProperty("db.main.password");
        final String url = this.env.getProperty("db.main.url");

        return this.getMysqlXADataSource(url, user, password);
    }

    /**
     * Batch data source.
     *
     * @return the data source
     */
    @Bean(name = "batchDataSource", initMethod = "init", destroyMethod = "close")
    public DataSource batchDataSource() {

        final String user = this.env.getProperty("db.batch.username");
        final String password = this.env.getProperty("db.batch.password");
        final String url = this.env.getProperty("db.batch.url");

        return this.getAtomikosDataSource("metaDataSource", this.getMysqlXADataSource(url, user, password));
    }

    /**
     * Gets the mysql xa data source.
     *
     * @param url the url
     * @param user the user
     * @param password the password
     * @return the mysql xa data source
     */
    private MysqlXADataSource getMysqlXADataSource(final String url, final String user, final String password) {

        final MysqlXADataSource mysql = new MysqlXADataSource();
        mysql.setUser(user);
        mysql.setPassword(password);
        mysql.setUrl(url);
        mysql.setPinGlobalTxToPhysicalConnection(true);

        return mysql;
    }

    /**
     * Gets the atomikos data source.
     *
     * @param resourceName the resource name
     * @param xaDataSource the xa data source
     * @return the atomikos data source
     */
    private AtomikosDataSourceBean getAtomikosDataSource(final String resourceName, final MysqlXADataSource xaDataSource) {

        final AtomikosDataSourceBean atomikos = new AtomikosDataSourceBean();
        atomikos.setUniqueResourceName(resourceName);
        atomikos.setXaDataSource(xaDataSource);
        atomikos.setMaxLifetime(3600);
        atomikos.setMinPoolSize(2);
        atomikos.setMaxPoolSize(10);

        return atomikos;
    }

}


package org.sample.settlement.batch;

import javax.sql.DataSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.DefaultBatchConfigurer;
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.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;

/**
 * The Class BatchConfiguration.
 *
 */
@Configuration
@EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {
    private final static Logger log = LoggerFactory.getLogger(BatchConfiguration.class);
    @Autowired private JobBuilderFactory jobs;
    @Autowired private StepBuilderFactory steps;
    @Autowired private PlatformTransactionManager transactionManager;
    @Autowired @Qualifier("processStep") private Step processStep;

    /**
     * Process payments job.
     *
     * @return the job
     */
    @Bean(name = "processJob")
    public Job processJob() {
        return this.jobs.get("processJob")
                    .incrementer(new RunIdIncrementer())
                    .start(processStep)
                    .build();
    }

    @Override
    @Autowired
    public void setDataSource(@Qualifier("batchDataSource") DataSource batchDataSource) {
        super.setDataSource(batchDataSource);
    }
}
6
Jared Knipp

Hast du sowas schon probiert?

@Bean(name="batchDataSource")
public DataSource batchDataSource(){          
       return DataSourceBuilder.create()
                .url(env.getProperty("batchdb.url"))
                .driverClassName(env.getProperty("batchdb.driver"))
                .username(env.getProperty("batchdb.username"))
                .password(env.getProperty("batchdb.password"))
                .build();          
} 

markieren Sie dann die andere Datenquelle mit einem @Primary und verwenden Sie einen @Qualifier in Ihrer Batch-Konfiguration, um anzugeben, dass Sie die Bean BatchDataSource automatisch verwenden möchten.

2
gyoder

Per https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto-two-datasources :

@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSourceProperties firstDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@Primary
@ConfigurationProperties("app.datasource.first")
public DataSource firstDataSource() {
    return firstDataSourceProperties().initializeDataSourceBuilder().build();
}

@Bean
@ConfigurationProperties("app.datasource.second")
public DataSourceProperties secondDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.second")
public DataSource secondDataSource() {
    return secondDataSourceProperties().initializeDataSourceBuilder().build();
}

In den Anwendungseigenschaften können Sie reguläre Datenquelleneigenschaften verwenden:

app.datasource.first.type=com.zaxxer.hikari.HikariDataSource
app.datasource.first.maximum-pool-size=30

app.datasource.second.url=jdbc:mysql://localhost/test
app.datasource.second.username=dbuser
app.datasource.second.password=dbpass
app.datasource.second.max-total=30
2
Philippe

Angenommen, Sie haben 2 Datenquellen, eine für Spring-Batch-Metadaten wie Jobdetails (z. B. CONFIGDB) und andere für Ihre Geschäftsdaten [z. B. AppDB]:

Fügen Sie CONFIGDB wie folgt in jobRepository ein:

 <bean id="jobRepository"
    class="org.springframework.batch.core.repository.support.JobRepositoryFactoryBean">
    <property name="transactionManager" ref="transactionManager" />
    <property name="dataSource" ref="CONFIGDB" />
    <property name="databaseType" value="db2" />
    <property name="tablePrefix" value="CONFIGDB.BATCH_" />
  </bean>

Jetzt können Sie die AppDB-Dartasource in die OR -Prozessoren Ihres DAOs einfügen, wenn dies der Fall ist.

   <bean id="DemoItemWriter" class="com.demoItemWriter">
     <property name="dataSource" ref="AppDB" />     
   </bean>

OR

sie können eine Ressource definieren und diese AppDB mit jndi-Lookup in die Klasse einfügen, in der sie benötigt wird:

public class ExampleDAO {

@Resource(lookup = "Java:comp/env/jdbc/AppDB")
DataSource ds;

}

1
Tejucb

Wie von Frozen in seine Antwort vorgeschlagen, haben zwei DataSources den Trick für mich getan. Zusätzlich musste ich ein BatchDataSourceInitializer definieren, um die Stapel-DataSource ordnungsgemäß zu initialisieren, wie in Michael Minellas Antwort auf diese verwandte Frage vorgeschlagen.

DataSource-Konfiguration

@Configuration
public class DataSourceConfiguration {

    @Bean
    @Primary
    @ConfigurationProperties("domain.datasource")
    public DataSource domainDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean("batchDataSource")
    @ConfigurationProperties("batch.datasource")
    public DataSource batchDataSource() {
        return DataSourceBuilder.create().build();
    }
}

Stapelkonfiguration

@Configuration
@EnableBatchProcessing
public class BatchConfiguration extends DefaultBatchConfigurer {

    @Override
    @Autowired
    public void setDataSource(@Qualifier("batchDataSource") DataSource batchDataSource) {
        super.setDataSource(batchDataSource);
    }

    @Bean
    public BatchDataSourceInitializer batchDataSourceInitializer(@Qualifier("batchDataSource") DataSource batchDataSource,
            ResourceLoader resourceLoader) {
        return new BatchDataSourceInitializer(batchDataSource, resourceLoader, new BatchProperties());
    }

application.properties:

# Sample configuraion using a H2 in-memory DB
domain.datasource.jdbcUrl=jdbc:h2:mem:domain-ds;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
domain.datasource.username=sa
domain.datasource.password=
domain.datasource.driver=org.h2.Driver

batch.datasource.jdbcUrl=jdbc:h2:mem:batch-ds;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
batch.datasource.username=sa
batch.datasource.password=
batch.datasource.driver=org.h2.Driver
0
C. Asselmeyer