TIPS |
xxxx |
JOB: 複数のStepをまとめたもので、バッチ処理全体の流れを定義する単位。
STEP: JOB内で実行される処理の単位。1つのSTEPは、処理ロジックを1つ持ち、chunk型またはtasklet型で実装される。
[Chunk(Job-Step)]一定件数ずつ繰り返すバッチ処理に向いた構成。
StepBuilderFactoryで以下を指定する。
.chunk(件数):1回のトランザクションで処理する件数を指定
ItemReader: データの読み取り
ItemProcessor: データの変換・加工(任意)
ItemWriter: データの書き込み
トランザクション単位: Chunk単位(指定した件数ごと)
(向いている処理)
大量データの分割処理、同じ処理を繰り返すタイプ(CSV→DB登録など)、トランザクション効率の最適化が必要な処理
[Tasklet]一括で処理を行う単純なバッチ処理に向いた構成。
StepBuilderFactoryで以下を指定する。
.tasklet(): Tasklet実装クラスを指定
Tasklet.execute() 内で処理全体を記述(chunk型のReader/Processor/Writerをすべて一体化)
トランザクション単位: Tasklet全体で1トランザクション
(向いている処理)
単発・シンプルな処理(ファイル削除、フラグ更新など)、前後処理(初期化、後片付け)、API呼び出し、ログ出力など
|
設計時に気をつけること |
(カテゴリ) (気をつけること)
・再実行設計 冪等性の確保、フラグ・チェックを入れる
・トランザクション Chunk単位で処理されるので、巻き戻しを意識
・パラメータ設計 JobInstanceが正しく一意になるようにする
・排他制御 多重実行を避ける工夫(ジョブ名やキーによる排他など)
・エラーハンドリング ログ出力と例外処理、失敗時の処理も設計しておく
・管理テーブル 肥大化に注意。運用ルールを決めておく
・通知・監視 成功/失敗時の通知、ジョブ監視の導入
|
管理テーブル |
BATCH_JOB_INSTANCE バッチジョブの「インスタンス」(入力パラメータに応じた単位)
BATCH_JOB_EXECUTION ジョブの1回ごとの実行履歴
BATCH_JOB_EXECUTION_PARAMS ジョブに渡したパラメータ
BATCH_STEP_EXECUTION 各Stepの実行履歴
BATCH_STEP_EXECUTION_CONTEXT Stepの実行コンテキストデータ(状態)
BATCH_JOB_EXECUTION_CONTEXT ジョブ全体の実行コンテキストデータ
BATCH_JOB_INSTANCE ジョブインスタンスの定義
|
[Spring Batch]ジョブ実行時にリターンコード(終了コード) |
正常終了(COMPLETED) → 0
異常終了(FAILEDなど) → 1以上の値
(参考)
Spring Bootの場合、ExitCodeGeneratorにより終了コードが決定される。
Sring Boot以外(Spring)の場合でも、CommandLineJobRunnerにより、異常終了コードは0以外になる。
|
構成 |
project-root/
├── src/
│ └── main/
│ ├── java/com/example/batch/
│ │ ├── config/ ← Job/Step定義
│ │ ├── domain/ ← エンティティやDTO
│ │ ├── processor/ ← ItemProcessor
│ │ ├── reader/ ← ItemReader
│ │ ├── writer/ ← ItemWriter
│ │ ├── runner/ ← 手動起動系(任意)
│ │ └── BatchApplication.java ← メインクラス
│ └── resources/
│ ├── application.yaml ← ★ これが設定の心臓部!
│ └── logback-spring.xml ← ログ設定(任意)
└── pom.xml
|
PGサンプル(chunk) |
// Job => Step
// Step = reader → processor(I→O) → writer
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// JobConfig JobとStepの定義を作成
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
@Configuration
//@RequiredArgsConstructor(byLombok)だと@Autowired(ただしfinal付き)しなくても良いらしい。
public class UserImportJobConfig {
@Autowired
private final JobBuilderFactory jobBuilderFactory;
@Autowired
private final StepBuilderFactory stepBuilderFactory;
private final UserImportReader reader;
private final UserImportProcessor processor;
private final UserWriter writer;
@Bean
public Job userImportJob() {
return jobBuilderFactory.get("userImportJob")
.start(userImportStep())
.build();
}
@Bean
public Step userImportStep() {
return stepBuilderFactory.get("userImportStep")
.<UserImport, User>chunk(10) // 10件ずつCommitする設定
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// DTO
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
@Data
public class UserImport {
private Long id;
private String name;
private String email;
private boolean imported;
}
@Data
public class User {
private Long id;
private String name;
private String email;
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Reader
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
@Component
public class UserImportReader implements ItemReader<UserImport> {
@Autowired
private final UserImportMapper userImportMapper;
@Autowired
private Iterator<UserImport> userIterator;
@Override
public UserImport read() {
if (userIterator == null) {
List<UserImport> users = userImportMapper.findUnimportedUsers();
userIterator = users.iterator();
}
return userIterator.hasNext() ? userIterator.next() : null;
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Processor Inputデータを変換する役割
// 以下の例ではUserImport型からUser型に変換
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
@Component
public class UserImportProcessor implements ItemProcessor<UserImport, User> {
@Override
public User process(UserImport item) {
User user = new User();
user.setName(item.getName());
user.setEmail(item.getEmail());
return user;
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Writer chunkの指定数のListが渡される
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
@Component
public class UserWriter implements ItemWriter {
@Autowired
private final UserMapper userMapper;
@Autowired
private final UserImportMapper userImportMapper;
@Override
public void write(List<? extends User> users) {
for (User user : users) {
// 本登録
userMapper.insert(user);
// フラグ更新(処理中から処理済みに更新)
userImportMapper.markAsImported(user.getEmail());
}
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Mapper(MyBatis)
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
@Mapper
public interface UserImportMapper {
List<UserImport> findUnimportedUsers();
void markAsImported(String email);
}
@Mapper
public interface UserMapper {
void insert(User user);
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// 起動クラス
// SpringApplication.run(...)
// ApplicationContext起動
// JobLauncherCommandLineRunner実行
// @BeanのJOBが起動
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BatchApplication {
// application.propertiesに以下の設定を起動する
// spring.batch.job.names=userImportJob
public static void main(String[] args) {
SpringApplication.run(BatchApplication.class, args);
}
}
|
PGサンプル(tasklet) |
// Job => Step
// Step = Tasklet
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// JobConfig JobとStepの定義を作成
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
@Configuration
public class FileDeleteJobConfig {
@Autowired
private JobBuilderFactory jobBuilderFactory;
@Autowired
private StepBuilderFactory stepBuilderFactory;
@Autowired
private FileDeleteTasklet fileDeleteTasklet;
@Bean
public Job fileDeleteJob() {
return jobBuilderFactory.get("fileDeleteJob")
.start(fileDeleteStep())
.build();
}
@Bean
public Step fileDeleteStep() {
return stepBuilderFactory.get("fileDeleteStep")
.tasklet(fileDeleteTasklet)
.build();
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Tasklet
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import org.springframework.batch.core.StepContribution;
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;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@Component
public class FileDeleteTasklet implements Tasklet {
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
Path filePath = Paths.get("/tmp/temp.txt");
if (Files.exists(filePath)) {
Files.delete(filePath);
System.out.println("ファイルを削除しました。");
} else {
System.out.println("ファイルが存在しません。");
}
return RepeatStatus.FINISHED;
}
}
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// 起動クラス
// SpringApplication.run(...)
// ApplicationContext起動
// JobLauncherCommandLineRunner実行
// @BeanのJOBが起動
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TaskletBatchApplication {
public static void main(String[] args) {
// application.propertiesに以下の設定を起動する
// spring.batch.job.names=fileDeleteJob
SpringApplication.run(TaskletBatchApplication.class, args);
}
}
|
起動チェック |
1.@SpringBootApplicationがある
メインクラスに定義されていること
2.Job が @Bean で定義されている
JobBuilderFactory.get("jobName") を使って作成
3.application.yaml で spring.batch.job.enabled: true
省略してもtrue(デフォルト)
4.Job
Jobが1つの場合:明示的にjob名指定しなくても起動される
Jobが複数ある場合:--spring.batch.job.names=jobName で明示必要
|
maven(pom.xml) |
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<!-- JDBC経由でDBアクセスするなら(例:H2 or PostgreSQLなど) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok(@RequiredArgsConstructorなど) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- テスト用(任意) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
|
[リリース時]logback-spring.xmlの場所を指定する |
set LOGGING_CONFIG=file:C:\path\to\logback-spring.xml
java -jar my-batch.jar
|
[リリース時]application.yaml/application.propertiesの場所 |
設定ファイル(application.yaml/.properties)を探す順番
1.jarと同じディレクトリ
2.jarのあるディレクトリのconfigサブディレクトリ
3.[--spring.config.location]で指定する
|