Spring Bootの概要及び必要知識
(1)Maven
- javaプログラムをコンパイル・テスト・パッケージング・デプロイを行うツール
- 使用するJarライブラリ群の依存性を管理
- pom.xmlという設定ファイルに、プロジェクトの情報、ライブラリ、プラグイン等を記述する
(2)Javaアノテーション
- 『注釈』の意、アノテーション型
- 補足として機能したり、フレームワークに意図を伝える(処理を指示)できる
- フレームワークで用意されたアノテーションを使えるようになることが重要
- アノテーションには引数を渡すことができる
(3)Dependency Injection
- 一般的なjavaプログラムでは、あるクラスから他のクラスを呼び出す時にはnew(インスタンス化)する必要があり、これを依存性のある状態と呼ぶ
- インスタンスの生成をSpring Boot(DIコンテナ)にまかせ、インスタンスの生成、参照の解決を自動で行ってもらう事を指す。依存性をDIコンテナが管理し、適用してくれる
- @Autowaired というアノテーションを記述するだけで、Spring Boot管理下にある他のクラスを呼び出せる
Spring Batchの概要
- 大規模なデータ処理を行う、バッチ処理向けのフレームワーク
- Spring Frameworkの一部として提供され、Springの機能を利用して効率的にバッチ処理を開発出来る
- javaでバッチを開発する場合のデファクトスタンダート
※ バッチ処理とは、あらかじめ一連の処理手順を登録しておき、自動的に処理を行う方式のこと
Spring Batch 全体概要
JobLauncher ⇨ Job Repository ⇨ Database
⇩
Job (一連のステップの集合) ⇨ Job Repository ⇨ Database
⇩
Step (バッチ処理の単位) ⇨ Tasklet
⇨ Platform Transaction Manager ⇨ Database
⇩
Chunk 『Item Reader』『Item Processor』『Item Writer』
- JobLauncher : ジョブの実行を管理(ジョブを起動して、ジョブ全体の成功または失敗を判定)
- Job : バッチ処理の単位(顧客データの取り込みや月次レポートの作成)
- Step : ジョブ内で実行される処理の単位(処理内容を記述するタスクレッドとチャンクと呼び出す)
- tasklet : タスクレッドはステップ内で実行される処理の最小単位(具体的な処理内容を記述)
- Chunk : 『Item Reader』『Item Processor』『Item Writer』を組み合わせてデータ処理を行う
- Item Reader : データを読み込む
- Item Processor : 読み込んだデータを処理する
- Item Writer : 処理を書き込む
- Job Repository : ジョブの実行履歴を保存する
- Platform Transaction Manager : トランザクションの境界を定義してロールバックコミットを制御する
Spring Batchを設定する
(1)プロジェクトを作成する
① ブラウザを開き、(https://start.spring.io/) を入力し、開く
② 下記を入力し、Dependenciesで下記を選択し、『GENERATE』ボタンを押す
-
Project: Maven
-
Language: Java
-
SpringBoot: 3.2.9
-
jar
-
21
-
Group: com.udemy
-
Artifact: hello-spring-batch
-
Name : hello-spring-batch
-
Description: Demo projects for Spring Boot
-
Package name: com.udemy.hello
Dependencies
- Spring Batch
- PostgreSQL Driver
- Lombok
(2)Spring Batch設定Configクラスを作成する
①config/SpringConfig.javaを作成する
- Spring Batchでは、@Configurationを付与をした設定用のコンフィグクラスを作成する
- バッチ処理の全体構成・設定を記述する
package com.udemy.hello_spring_batch_2.config;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
// @Configurationアノテーションを付与する
@Configuration
public class SpringConfig {
// JobLauncher/JobRepository/PlatformTransactionManagerのクラス変数及びコンストラクタによる設定を記載する
private final JobLauncher jobLauncher;
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
public SpringConfig(JobLauncher jobLauncher, JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
this.jobLauncher = jobLauncher;
this.jobRepository = jobRepository;
this.transactionManager = transactionManager;
}
}
(3)tasklet/HelloTasklet1.javaを作成する
①tasklet/HelloTasklet1.javaを作成する
- Taskletインターフェースを実装し、Taskletを定義する
※Taskletとは、Step内で実行される処理を記述するためのインターフェースで、execute()メソッドを実装することで、具体的な処理を記述する
package com.udemy.hello_spring_batch_2.tasklet;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
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.stereotype.Component;
//@Componentアノテーションによって、Spring Beanとして登録される
@Component("Hello Tasklet1")
// StepScopeアノテーションによって、インスタンスをステップごとに生成・破棄する
@StepScope
// Tasklet インターフェイスexecuteメソッドを実装する
public class HelloTasklet1 implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
System.out.println("Hello Tasklet1!!!");
// RepeatStatus.FINISHEDは、executeメソッドが正常終了したことを示す
return RepeatStatus.FINISHED;
}
}
②Taskletをバッチ設定クラスに定義する
- 作成したTaskletはバッチ設定クラスに定義し、上位のStepから呼び出されるように準備する
package com.udemy.hello_spring_batch_2.config;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
// @Configurationアノテーションを付与する
@Configuration
public class SpringConfig {
// JobLauncher/JobRepository/PlatformTransactionManagerのクラス変数及びコンストラクタによる設定を記載する
private final JobLauncher jobLauncher;
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
// @Autowiredアノテーション、@Qualifierアノテーションを使用して、『HelloTasklet1』と名前がついたBeanのインスタンスを自動で注入する
@Autowired
@Qualifier("HelloTasklet1")
private Tasklet helloTasklet1;
public SpringConfig(JobLauncher jobLauncher, JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
this.jobLauncher = jobLauncher;
this.jobRepository = jobRepository;
this.transactionManager = transactionManager;
}
}
③config/SpringConfig.javaにStepとjobを定義する
※ Jobとは、①バッチ処理の単位(顧客データ取込、月次レポート作成等)を表し、複数のStepをまとめて実行できる。②実行するステップの情報・バッチ処理にパラメータを設定した場合のパラメータチェックを設定できる。
※ Stepとは、Job内で実行される処理の単位を表し、Taskletもしくは、Chunkにおける各処理(読取・加工・書込)を設定する。
Spring Batchを実行する
- 実行時にはSpring Batchの内部機能として、ジョブの実行状態・履歴等の情報がデータベースに格納される。
①application.propertiesにDB接続情報を記載する
spring.datasource.url=jdbc:mysql://localhost:3306/BatchDB?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.batch.jdbc.initialize-schema = ALWAYS
②tasklet/HelloTasklet1.javaにジョブに対してパラメータ(引数)を与える
- JobExecutionContextは、Job実行中に共有されるデータストアで、Tasklet間で値を受け渡したい場合は、JobExecutionContextに値を保存し、別のTaskletで取得する。
package com.udemy.hello.tasklet;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
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.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component("HelloTasklet1")
@StepScope
@Slf4j
public class HelloTasklet1 implements Tasklet {
// @Valueアノテーションを指定
@Value("#{jobParameters['param1']}")
private String param1;
@Value("#{jobParameters['param2']}")
private Integer param2;
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throws Exception {
log.info("Hello Tasklet1");
log.info("param1={}", param1); // パラメータ埋め込み修正
log.info("param2={}", param2); // パラメータ埋め込み修正
// System.out.println("Hello Tasklet1");
// System.out.println("param1=" + param1);
// System.out.println("param2=" + param2);
// JobExecutionContextを取得
ExecutionContext jobContext = contribution.getStepExecution()
.getJobExecution()
.getExecutionContext();
jobContext.put("jobKey1", "jobValue1");
return RepeatStatus.FINISHED;
}
}
③validator/HelloJobParametersValidator.javaを作成する
- バラメータ検証を行うクラスを作成し、JobParametersValidatorインターフェイスを実装することで実現できる
package com.udemy.hello.validator;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.JobParametersValidator;
public class HelloJobParametersValidator implements JobParametersValidator {
@Override
public void validate(JobParameters parameters)
throws JobParametersInvalidException {
// パラメータ1の値チェック
String param1 = parameters.getString("param1");
if (!param1.equals("DEV") &&
!param1.equals("TEST") &&
!param1.equals("PROD")) {
throw new JobParametersInvalidException("param1:" + param1
+ "DEV/TEST/PRODのいずれかを指定してください");
}
// パラメータ2の値チェック
String param2 = parameters.getString("param2");
// 数値であることをチェック
try {
Integer.parseInt(param2);
} catch (Exception e) {
throw new JobParametersInvalidException("param2=" + param2
+ "paramsは数値を指定してください。");
}
}
}
④tasklet/HelloTasklet2.javaを作成する
package com.udemy.hello.tasklet;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.StepScope;
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.Value;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component("HelloTasklet2")
@StepScope
@Slf4j
public class HelloTasklet2 implements Tasklet {
@Value("#{JobExecutionContext['jobKey1']}")
private String jobValue1;
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throws Exception {
log.info("Hello Tasklet2");
log.info("jobValue1={}" + jobValue1);
// System.out.println("Hello Tasklet2");
// System.out.println("jobValue1=" + jobValue1);
return RepeatStatus.FINISHED;
}
}
⑤resourcesにlogback.xmlを作成する
- Lombokとは、Javaのコードを簡潔にし、生産性を高め、可読性を高めるオープンソースのライブラリ
- @Dataアノテーションで、Getter/Setterを書かなくても使用できる
- @Slf4jのアノテーションをつけることでログ出力の実装が可能となる
Chunkを作成する
-
Chunkとは、ItemReader、ItemProcessor、ItemWriterを組み合わせて、データ処理を行う単位で、ItemReaderからデータを読み込み、ItemProcessorで処理し、ItemWriterで書き込む
-
ItemProcessorとは、ItemReaderから受け取ったデータ項目を処理し、ItemWriterを渡す
-
ItemWriterとは、ItemProcessorから受け取った処理結果を書き込む
①chunk/HelloReader.javaを作成する
package com.udemy.hello.chunk;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.NonTransientResourceException;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@StepScope
@Slf4j
public class HelloReader implements ItemReader<String> {
private int intIndex = 0;
@Override
public String read() throws Exception,
UnexpectedInputException, ParseException,
NonTransientResourceException {
String[] readDataList = { "associate", "consultant", "manager",
"director", "president", null };
log.info("Read Data={}", readDataList[intIndex]);
return readDataList[intIndex++];
}
}
②chunk/HelloProcessor.javaを作成する
package com.udemy.hello.chunk;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@StepScope
@Slf4j
public class HelloProcessor implements ItemProcessor<String, String> {
@Override
public String process(String item) throws Exception {
item = item.toUpperCase();
log.info("Processor item={}", item);
return item;
}
}
③chunk/HelloWriter.javaを作成する
package com.udemy.hello.chunk;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@StepScope
@Slf4j
public class HelloWriter implements ItemWriter<String> {
@Override
public void write(Chunk<? extends String> chunk)
throws Exception {
log.info("Writer chunk={}", chunk);
log.info("-----------------");
}
}
EventListenerを作成する
①listener/HelloJobExecutionListener.javaを作成する
package com.udemy.hello.listener;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class HelloJobExecutionListener implements JobExecutionListener {
@Override
public void beforeJob(JobExecution jobExecution) {
log.info("Job スタート at:{}", jobExecution.getStartTime());
}
@Override
public void afterJob(JobExecution jobExecution) {
log.info("Job 終了 at:{}", jobExecution.getEndTime());
}
}
CSVファイルを読み込みDBへ登録するバッチを作成する
(1)プロジェクトを作成する
① ブラウザを開き、(https://start.spring.io/) を入力し、開く
② 下記を入力し、Dependenciesで下記を選択し、『GENERATE』ボタンを押す
-
Project: Maven
-
Language: Java
-
SpringBoot: 3.2.9
-
jar
-
21
-
Group: com.udemy
-
Artifact: hello-spring-batch
-
Name : hello-spring-batch
-
Description: Demo projects for Spring Boot
-
Package name: com.udemy.hello
Dependencies
- Spring Batch
- PostgreSQL Driver
- lombock
CSVファイルを読み込む
①src/main/resoucesにinputData/employee.csvを作成する
従業員コード,従業員名,ジョブタイトル,上司,入社日
5000,山田 太郎,manager,5003,2001/01/01
5001,佐藤 二郎,salesman,5000,2002/05/02
5002,田中 三郎,associate,5001,2010/09/20
5003,鈴木 一郎,srmgr,,2020/01/01
②resouces/application.propertiesにDB接続文字列を追加する
# csvパスのエントリを追加する
csv.path=inputData/employee.csv
spring.datasource.url=jdbc:mysql://localhost:3306/BatchDB?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.batch.jdbc.initialize-schema = ALWAYS
③resouces/logback.xmlを追加する
<configuration>
<property name="logDir" value="C:\pleiades-2023\workspace\hello-spring-batch\log\" />
<property name="fileName" value="hellolog" />
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logDir}${fileName}.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>10</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d %level %m %n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
CSVデータを保持するクラスを作成する
①src/main/resoucesにmodel/Employee.javaを作成する
package com.udemy.hello.model;
import java.util.Date;
import lombok.Data
public class Employee {
private Integer empNumber;
private String empName;
private String jobTitle;
private Integer mgrNumber;
private Date hireDate;
}
②configCvs/SpringConfig.javaを作成する
package com.udemy.hello.configCsv;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.database.BeanPropertyItemSqlParameterSourceProvider;
import org.springframework.batch.item.database.JdbcBatchItemWriter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.core.io.ClassPathResource;
import java.nio.charset.StandardCharsets;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.job.builder.JobBuilder;
import javax.sql.DataSource;
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import com.udemy.hello.model.Employee;
@Configuration
public class SpringConfig {
private final JobLauncher jobLauncher;
private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
@Value("${csv.path}")
private Resource inputCSV;
@Autowired
private DataSource dataSource;
private static final String INSERT_EMP_SQL = "INSERT INTO employee (empNumber, empName, jobTitle, mgrNumber, hireDate) "
+
"VALUES (empNumber, empName, jobTitle, mgrNumber, hireDate)";
public SpringConfig(JobLauncher jobLauncher, JobRepository jobRepository,
PlatformTransactionManager transactionManager) {
this.jobLauncher = jobLauncher;
this.jobRepository = jobRepository;
this.transactionManager = transactionManager;
}
@Bean
@StepScope
public FlatFileItemReader<Employee> csvItemReader() {
FlatFileItemReader<Employee> reader = new FlatFileItemReader<Employee>();
reader.setResouce(inputCSV);
reader.setLinesToSkip(1);
reader.setEncoding(StandardCharsets.UTF_8.name());
BeanWrapperFieldSetMapper<Employee> beanWrapperFieldSetMapper = new BeanWrapperFieldSetMapper<Employee>();
beanWrapperFieldSetMapper.setTargetType(Employee.class);
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
String[] csvTitleArray = new String[] { "EmpNumber", "EmpName", "JobTitle", "mgrNumber", "HireDate" };
tokenizer.setNames(csvTitleArray);
DefaultLineMapper<Employee> lineMapper = new DefaultLineMapper<Employee>();
lineMapper.setFieldSetMapper(beanWrapperFieldSetMapper);
lineMapper.setLineTokenizer(tokenizer);
reader.setLineMapper(lineMapper);
return reader;
}
@Autowired
@Qualifier("empItemProcessor")
public ItemProcessor<Employee, Employee> empItemProcessor;
@Bean
@StepScope
public JdbcBatchItemWriter<Employee> jdbcBatchItemWriter() {
BeanPropertyItemSqlParameterSourceProvider<Employee> provider = new BeanPropertyItemSqlParameterSourceProvider<Employee>();
JdbcBatchItemWriter<Employee> writer = new JdbcBatchItemWriter<Employee>();
writer.setDataSource(dataSource);
writer.setItemSqlParameterSourceProvider(provider);
writer.setSql(INSERT_EMP_SQL);
return writer;
}
@Bean
public Step chunkStep1() {
return new StepBuilder("EmpImportStep1", jobRepository)
.<Employee, Employee>chunk(1, transactionManager)
.reader(csvItemReader())
.processor(empItemProcessor)
.writer(jdbcItemWriter())
.build();
}
@Bean
public Job chunkJob() {
return new JobBuilder("chunkJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(chunkStep1())
.build();
}
}
③chuckCsv/EmpItemProcessor.javaを作成する
package com.udemy.hello.chuckCsv;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;
import com.udemy.hello.model.Employee;
import lombok.extern.slf4j.Slf4j;
@Component("EmpItemProcessor")
@StepScope
@Slf4j
public class EmpItemProcessor implements ItemProcessor<Employee, Employee> {
@Override
public Employee process(Employee item) throws Exception {
log.info("Proceess...{}", item);
item.setJobTitle(item.getJobTitle().toUpperCase());
return item;
}
}