Spring Boot + Spring Batch(アノテーション)を使用していますが、2つのジョブを実行する必要があるシナリオに遭遇しました。
SpringBatchを使用して更新する必要がある従業員と給与のレコードがあります。このチュートリアルに従ってBatchConiguration
クラスを構成しました 春のバッチ入門チュートリアル それぞれBatchConfigurationEmployeeとBatchConfigurationSalaryという名前のEmployeeオブジェクトとSalaryオブジェクト用。
すでに説明したチュートリアルに従って、ItemReader
、ItemProcessor
、ItemWriter
、およびJob
を定義しました。
Spring Bootアプリケーションのいずれかを実行するときに、両方のBatchConfiguredクラスを実行したいと思います。どうすればこれを達成できますか
********* BatchConfigurationEmployee.Java *************
@Configuration
@EnableBatchProcessing
public class BatchConfigurationEmployee {
public ItemReader<employee> reader() {
return new EmployeeItemReader();
}
@Bean
public ItemProcessor<Employee, Employee> processor() {
return new EmployeeItemProcessor();
}
@Bean
public Job Employee(JobBuilderFactory jobs, Step s1) {
return jobs.get("Employee")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.build();
}
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Employee> reader,
ItemProcessor<Employee, Employee> processor) {
return stepBuilderFactory.get("step1")
.<Employee, Employee> chunk(1)
.reader(reader)
.processor(processor)
.build();
}
}
給与クラスはこちら
@Configuration
@EnableBatchProcessing
public class BatchConfigurationSalary {
public ItemReader<Salary> reader() {
return new SalaryItemReader();
}
@Bean
public ItemProcessor<Salary, Salary> processor() {
return new SalaryItemProcessor();
}
@Bean
public Job salary(JobBuilderFactory jobs, Step s1) {
return jobs.get("Salary")
.incrementer(new RunIdIncrementer())
.flow(s1)
.end()
.build();
}
@Bean
public Step step1(StepBuilderFactory stepBuilderFactory, ItemReader<Salary> reader,
ItemProcessor<Salary, Salary> processor) {
return stepBuilderFactory.get("step1")
.<Salary, Salary> chunk(1)
.reader(reader)
.processor(processor)
.build();
}
}
Beanの名前は、Springコンテキスト全体で一意である必要があります。
どちらのジョブでも、同じメソッド名でリーダー、ライター、プロセッサーをインスタンス化しています。 methodnameは、コンテキスト内のBeanを識別するために使用される名前です。
両方のジョブ定義には、reader()、writer()、およびprocessor()があります。それらは互いに上書きします。それらにreaderEmployee()、readerSalary()などの一意の名前を付けます。
それはあなたの問題を解決するはずです。
ジョブには@Beanアノテーションが付けられていないため、spring-contextはそれらを認識しません。
JobLauncherCommandLineRunnerクラスを見てください。 Jobインターフェースを実装するSpringContext内のすべてのBeanが注入されます。見つかったすべてのジョブが実行されます。 (これは、JobLauncherCommandLineRunnerのexecuteLocalJobsメソッド内で発生します)
何らかの理由で、それらをコンテキスト内のBeanとして使用したくない場合は、ジョブをjobregistryに登録する必要があります(JobLauncherCommandLineRunnerのregisteredJobsを実行するメソッドは、登録されたジョブの起動を処理します)
ところで、あなたはプロパティで制御することができます
spring.batch.job.names= # Comma-separated list of job names to execute on startup (For instance
`job1,job2`). By default, all Jobs found in the context are executed.
どのジョブを開始する必要があります。
これも複数のジョブを実行するためのかなり良い方法だと思います。
Job Launcherを使用してジョブを構成および実行し、独立したcommandLineRunner実装を使用してそれらを実行しています。これらは、必要なときに順番に実行されるように順序付けられていますが
大きな投稿についてお詫びしますが、複数のコマンドラインランナーを使用したJobLauncher構成を使用して何が達成できるかを明確に示したいと思いました。
これは私が持っている現在のBeanConfigurationです
@Configuration
public class BeanConfiguration {
@Autowired
DataSource dataSource;
@Autowired
PlatformTransactionManager transactionManager;
@Bean(name="jobOperator")
public JobOperator jobOperator(JobExplorer jobExplorer,
JobRegistry jobRegistry) throws Exception {
SimpleJobOperator jobOperator = new SimpleJobOperator();
jobOperator.setJobExplorer(jobExplorer);
jobOperator.setJobRepository(createJobRepository());
jobOperator.setJobRegistry(jobRegistry);
jobOperator.setJobLauncher(jobLauncher());
return jobOperator;
}
/**
* Configure joblaucnher to set the execution to be done asycn
* Using the ThreadPoolTaskExecutor
* @return
* @throws Exception
*/
@Bean
public JobLauncher jobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(createJobRepository());
jobLauncher.setTaskExecutor(taskExecutor());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
// Read the datasource and set in the job repo
protected JobRepository createJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setIsolationLevelForCreate("ISOLATION_SERIALIZABLE");
//factory.setTablePrefix("BATCH_");
factory.setMaxVarCharLength(10000);
return factory.getObject();
}
@Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder().additionalInterceptors(new CustomRestTemplateLoggerInterceptor());
}
@Bean(name=AppConstants.JOB_DECIDER_BEAN_NAME_EMAIL_INIT)
public JobExecutionDecider jobDecider() {
return new EmailInitJobExecutionDecider();
}
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(15);
taskExecutor.setMaxPoolSize(20);
taskExecutor.setQueueCapacity(30);
return taskExecutor;
}
}
Postgreでジョブ実行の詳細を保持するようにデータベースを設定したため、DatabaseConfigurationは次のようになります(2つの異なるプロファイル用の2つの異なるBean -env)
@ConfigurationパブリッククラスDatasourceConfigurationはEnvironmentAware {を実装します
private Environment env;
@Bean
@Qualifier(AppConstants.DB_BEAN)
@Profile("dev")
public DataSource getDataSource() {
HikariDataSource ds = new HikariDataSource();
boolean isAutoCommitEnabled = env.getProperty("spring.datasource.hikari.auto-commit") != null ? Boolean.parseBoolean(env.getProperty("spring.datasource.hikari.auto-commit")):false;
ds.setAutoCommit(isAutoCommitEnabled);
// Connection test query is for legacy connections
//ds.setConnectionInitSql(env.getProperty("spring.datasource.hikari.connection-test-query"));
ds.setPoolName(env.getProperty("spring.datasource.hikari.pool-name"));
ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
long timeout = env.getProperty("spring.datasource.hikari.idleTimeout") != null ? Long.parseLong(env.getProperty("spring.datasource.hikari.idleTimeout")): 40000;
ds.setIdleTimeout(timeout);
long maxLifeTime = env.getProperty("spring.datasource.hikari.maxLifetime") != null ? Long.parseLong(env.getProperty("spring.datasource.hikari.maxLifetime")): 1800000 ;
ds.setMaxLifetime(maxLifeTime);
ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
ds.setPoolName(env.getProperty("spring.datasource.hikari.pool-name"));
ds.setUsername(env.getProperty("spring.datasource.username"));
ds.setPassword(env.getProperty("spring.datasource.password"));
int poolSize = env.getProperty("spring.datasource.hikari.maximum-pool-size") != null ? Integer.parseInt(env.getProperty("spring.datasource.hikari.maximum-pool-size")): 10;
ds.setMaximumPoolSize(poolSize);
return ds;
}
@Bean
@Qualifier(AppConstants.DB_PROD_BEAN)
@Profile("prod")
public DataSource getProdDatabase() {
HikariDataSource ds = new HikariDataSource();
boolean isAutoCommitEnabled = env.getProperty("spring.datasource.hikari.auto-commit") != null ? Boolean.parseBoolean(env.getProperty("spring.datasource.hikari.auto-commit")):false;
ds.setAutoCommit(isAutoCommitEnabled);
// Connection test query is for legacy connections
//ds.setConnectionInitSql(env.getProperty("spring.datasource.hikari.connection-test-query"));
ds.setPoolName(env.getProperty("spring.datasource.hikari.pool-name"));
ds.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
long timeout = env.getProperty("spring.datasource.hikari.idleTimeout") != null ? Long.parseLong(env.getProperty("spring.datasource.hikari.idleTimeout")): 40000;
ds.setIdleTimeout(timeout);
long maxLifeTime = env.getProperty("spring.datasource.hikari.maxLifetime") != null ? Long.parseLong(env.getProperty("spring.datasource.hikari.maxLifetime")): 1800000 ;
ds.setMaxLifetime(maxLifeTime);
ds.setJdbcUrl(env.getProperty("spring.datasource.url"));
ds.setPoolName(env.getProperty("spring.datasource.hikari.pool-name"));
ds.setUsername(env.getProperty("spring.datasource.username"));
ds.setPassword(env.getProperty("spring.datasource.password"));
int poolSize = env.getProperty("spring.datasource.hikari.maximum-pool-size") != null ? Integer.parseInt(env.getProperty("spring.datasource.hikari.maximum-pool-size")): 10;
ds.setMaximumPoolSize(poolSize);
return ds;
}
public void setEnvironment(Environment environment) {
// TODO Auto-generated method stub
this.env = environment;
}
}
Jvmを正常にシャットダウンできるように、最初のアプリランチャーがアプリの実行をキャッチしていることを確認してください。アプリの実行は、ジョブの実行が終了すると(失敗するか、完了すると)返されます。それ以外の場合、joblauncherを使用すると、すべてのジョブが完了した後でもjvmが有効になります。
@SpringBootApplication
@ComponentScan(basePackages="com.XXXX.Feedback_File_Processing.*")
@EnableBatchProcessing
public class FeedbackFileProcessingApp
{
public static void main(String[] args) throws Exception {
ApplicationContext appContext = SpringApplication.run(FeedbackFileProcessingApp.class, args);
// The batch job has finished by this point because the
// ApplicationContext is not 'ready' until the job is finished
// Also, use System.exit to force the Java process to finish with the exit code returned from the Spring App
System.exit(SpringApplication.exit(appContext));
}
}
.............など、以下のような2つの異なる構成に対して、上記のように独自のディサイダー、独自のジョブ/ステップを構成し、コマンドラインランナーで別々に使用できます(投稿は大きくなって、私は仕事とコマンドラインランナーだけの詳細を与えています)
これらは2つの仕事です
@Configuration
public class DefferalJobConfiguration {
@Autowired
JobLauncher joblauncher;
@Autowired
private JobBuilderFactory jobFactory;
@Autowired
private StepBuilderFactory stepFactory;
@Bean
@StepScope
public Tasklet newSampleTasklet() {
return ((stepExecution, chunkContext) -> {
System.out.println("execution of step after flow");
return RepeatStatus.FINISHED;
});
}
@Bean
public Step sampleStep() {
return stepFactory.get("sampleStep").listener(new CustomStepExecutionListener())
.tasklet(newSampleTasklet()).build();
}
@Autowired
@Qualifier(AppConstants.FLOW_BEAN_NAME_EMAIL_INITIATION)
private Flow emailInitFlow;
@Autowired
@Qualifier(AppConstants.JOB_DECIDER_BEAN_NAME_EMAIL_INIT)
private JobExecutionDecider jobDecider;
@Autowired
@Qualifier(AppConstants.STEP_BEAN_NAME_ITEMREADER_FETCH_DEFERRAL_CONFIG)
private Step deferralConfigStep;
@Bean(name=AppConstants.JOB_BEAN_NAME_DEFERRAL)
public Job deferralJob() {
return jobFactory.get(AppConstants.JOB_NAME_DEFERRAL)
.start(emailInitFlow)
.on("COMPLETED").to(sampleStep())
.next(jobDecider).on("COMPLETED").to(deferralConfigStep)
.on("FAILED").fail()
.end().build();
}
}
@Configuration
public class TestFlowJobConfiguration {
@Autowired
private JobBuilderFactory jobFactory;
@Autowired
@Qualifier("testFlow")
private Flow testFlow;
@Bean(name = "testFlowJob")
public Job testFlowJob() {
return jobFactory.get("testFlowJob").start(testFlow).end().build();
}
}
コマンドラインランナーは次のとおりです(2番目のジョブが初期化される前に最初のジョブが完了することを確認していますが、異なる戦略に従ってそれらを並行して実行するのは完全にユーザー次第です)
@Component
@Order(1)
public class DeferralCommandLineRunner implements CommandLineRunner, EnvironmentAware{
// If the jobLauncher is not used, then by default jobs are launched using SimpleJobLauncher
// with default configuration(assumption)
// hence modified the jobLauncher with vales set in BeanConfig
// of spring batch
private Environment env;
@Autowired
JobLauncher jobLauncher;
@Autowired
@Qualifier(AppConstants.JOB_BEAN_NAME_DEFERRAL)
Job deferralJob;
@Override
public void run(String... args) throws Exception {
// TODO Auto-generated method stub
JobParameters jobparams = new JobParametersBuilder()
.addString("run.time", LocalDateTime.now().
format(DateTimeFormatter.ofPattern(AppConstants.JOB_DATE_FORMATTER_PATTERN)).toString())
.addString("instance.name",
(deferralJob.getName() != null) ?deferralJob.getName()+'-'+UUID.randomUUID().toString() :
UUID.randomUUID().toString())
.toJobParameters();
jobLauncher.run(deferralJob, jobparams);
}
@Override
public void setEnvironment(Environment environment) {
// TODO Auto-generated method stub
this.env = environment;
}
}
@Component
@Order(2)
public class TestJobCommandLineRunner implements CommandLineRunner {
@Autowired
JobLauncher jobLauncher;
@Autowired
@Qualifier("testFlowJob")
Job testjob;
@Autowired
@Qualifier("jobOperator")
JobOperator operator;
@Override
public void run(String... args) throws Exception {
// TODO Auto-generated method stub
JobParameters jobParam = new JobParametersBuilder().addString("name", UUID.randomUUID().toString())
.toJobParameters();
System.out.println(operator.getJobNames());
try {
Set<Long> deferralExecutionIds = operator.getRunningExecutions(AppConstants.JOB_NAME_DEFERRAL);
System.out.println("deferralExceutuibuds:" + deferralExecutionIds);
operator.stop(deferralExecutionIds.iterator().next());
} catch (NoSuchJobException | NoSuchJobExecutionException | JobExecutionNotRunningException e) {
// just add a logging here
System.out.println("exception caught:" + e.getMessage());
}
jobLauncher.run(testjob, jobParam);
}
}
これがどのようにそれができるかについての完全な考えを与えることを願っています。私はspring-boot-starter-batch:jar:2.0.0.RELEASEを使用しています