Spring Batch5 復習がてらサンタかサタンを降臨させる

# はじめに

最近Spring Batchの学習をしていました。
せっかくなのでハローワールドだけで終わらずに何か作ってみようかなと。
いつも何を作るか悩んでるうちに作るのをやめてしまうのですが、今回はすぐに思いつきました。
クリスマス近いな。なんかクリスマスっぽいの作ってみようと。

# 内容

A CSVの値をDBに登録するバッチの作成
B DBの値をCSVに書き込むバッチの作成
C 並列・並行処理の実装など

Aに関してはクリスマス要素ないので割愛
ソースは下記にあります。
https://github.com/wow74/samon-santa

# 動作

Bの動きとしては下記になります。

1. DBからデータを取得
1. 取得したデータを確認
    1. 12歳以下でいい人ならサンタが憑依し、プレゼントを生成、依り代から出ていきます。
    1. 悪い人には何もありません。
    1. かなり悪い人には、サタンが憑依し、寿命を吸い取り、依り代から出ていきます。
1. 2の内容を出力
1. DBから取得した情報をCSVに出力

サンタが憑依するということにしてあるのは、目撃情報少ないみたいだし、自分が寝ている間にサンタが憑依していれば自分は目撃不可だよなー。でも親は確認できるよなーって思ったので憑依にしています。カメラを仕掛けていれば悪い子なので憑依はしません。たぶん。

# DB

mysqlにsantaというDBを作成し、yorishiroテーブルを作成しています。
※依り代の英訳はyorishiroらしい
データはAでバリデーションチェックを通過したものを登録しています。

evaluationが0ならいい人、1なら悪い人、2ならかなり悪い人としています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/1d4b2ae7-c60b-6220-93af-2c0b76e1f33d.png)

# ファイル

- プロジェクト下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/bf6ef0d6-864b-eb44-15e8-e2fa9e07cb01.png)

data\yorishiro.csvはバッチ実行時に作成されます。

- src下
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/e53a24cb-24a3-6e67-9e4c-302e6160affa.png)

# ソース

``` build.gradle
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.4.0'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(23)
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-batch'
	runtimeOnly 'com.h2database:h2'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.springframework.batch:spring-batch-test'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
}

tasks.named('test') {
	useJUnitPlatform()
}
```
メタ情報とデータを分けたかったのでh2とmysqlの2つを使用しています。
※Spring Batchはデフォルトだと使用しているDBにバッチの実行結果が登録されます。
　下図はemployeeテーブルのみ作成していたけど、バッチ実行によってほかのテーブルが
 　勝手に作成された状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/9787ca1f-6a3d-046f-88ad-3b313980f113.png)

下図はメタ情報はh2に入れるようにしている状態
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/46071593-e9c4-bea5-c80f-8b9933b5fe58.png)

``` application.yml
spring:
  batch:
    job:
      name: ExportJob # Job名、Jobが一個しかないなら不要です
    jdbc:
      initialize-schema: always # alwaysにしないとh2でエラーになります
#      schema: classpath:org/springframework/batch/core/schema-mysql.sql # h2でエラーになります
  datasource:
    h2:
      driver-class-name: org.h2.Driver
      url: jdbc:h2:mem:santadb;DB_CLOSE_DELAY=-1
      username: sa
      password:
    mysql:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/santa?useSSL=false&serverTimezone=UTC
      username: root
      password:
csv:
  path: data/yorishiro.csv # ファイルの出力先
```

``` DemoApplication.java
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(DemoApplication.class, args);
	}

}
```

## domain.model\

``` Yorishiro.java
package com.example.demo.domain.model;

import lombok.Data;
import org.apache.logging.log4j.util.Strings;

@Data
public class Yorishiro {

  private int id;
  private String name;
  private int age;
  private int evaluation;
  private String evaluationString;

  public void evaluationToString() {
    evaluationString = switch (evaluation) {
      case 0 -> "良";
      case 1 -> "不良";
      case 2 -> "最悪";
      default -> null;
    };

    if (Strings.isEmpty(evaluationString)) throw new IllegalStateException("評価値が不正です。");

  }
}
```

evaluationToString()により、出力情報やcsvにevaluationの数値ではなく文字で出力させます。

## csv\

``` CsvHeaderCallback.java
package com.example.demo.csv;

import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.file.FlatFileHeaderCallback;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.Writer;

@Component
@StepScope
public class CsvHeaderCallback implements FlatFileHeaderCallback {

  @Override
  public void writeHeader(Writer writer) throws IOException {
    writer.write("ID,名前,年齢,評価");
  }
}
```
csvのヘッダ部の設定をします。

``` CsvFooterCallback.java
package com.example.demo.csv;

import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.file.FlatFileFooterCallback;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.Writer;

@Component
@StepScope
public class CsvFooterCallback implements FlatFileFooterCallback {

  @Value("#{StepExecution}")
  private StepExecution stepExecution;

  @Override
  public void writeFooter(Writer writer) throws IOException {
    writer.write("合計=" + stepExecution.getWriteCount() + "件");

  }
}
```
csvのフッター部の設定をします。

## listener\

``` ExecutionListener.java
package com.example.demo.listener;

import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobExecutionListener;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

public class ExecutionListener implements JobExecutionListener {

  private final ThreadPoolTaskExecutor executor;

  public ExecutionListener(ThreadPoolTaskExecutor executor) {
    this.executor = executor;
  }

  @Override
  public void afterJob(JobExecution jobExecution) {
    executor.shutdown();
  }
}
```
並列処理を実行すると処理が終了しなくなるためexecutor.shutdown()で処理を終了させます。

``` ReadListener.java
package com.example.demo.listener;

import com.example.demo.domain.model.Yorishiro;
import org.springframework.batch.core.ItemReadListener;
import org.springframework.stereotype.Component;

@Component
public class ReadListener implements ItemReadListener<Yorishiro> {

  @Override
  public void beforeRead() {
    System.out.println("BeforeRead:");
  }

  @Override
  public void afterRead(Yorishiro item) {
    System.out.println("AfterRead: item=" + item);
  }

  @Override
  public void onReadError(Exception e) {
    System.out.println("ReadError: error=" + e.getMessage());
  }
}
```

データ読み込み時のリスナー処理を記載します。

``` ProcessListener.java
package com.example.demo.listener;

import com.example.demo.domain.model.Yorishiro;
import org.springframework.batch.core.ItemProcessListener;
import org.springframework.stereotype.Component;

@Component
public class ProcessListener implements ItemProcessListener<Yorishiro, Yorishiro> {

  @Override
  public void beforeProcess(Yorishiro item) {
    System.out.println("BeforeProcess: item=" + item);
  }

  @Override
  public void afterProcess(Yorishiro item, Yorishiro result) {
    System.out.println("AfterProcess: item=" + item + ",result=" + result);
  }

  @Override
  public void onProcessError(Yorishiro item, Exception e) {
    System.out.println("ProcessError: item=" + item + ",error=" + e.getMessage());
  }
}
```

データ処理時のリスナー処理を記載します。

``` WriteListener.java
package com.example.demo.listener;

import com.example.demo.domain.model.Yorishiro;
import org.springframework.batch.core.ItemWriteListener;
import org.springframework.batch.item.Chunk;
import org.springframework.stereotype.Component;

@Component
public class WriteListener implements ItemWriteListener<Yorishiro> {

  @Override
  public void beforeWrite(Chunk<? extends Yorishiro> items) {
    System.out.println("BeforeWrite: count=" + items.size());
  }

  @Override
  public void afterWrite(Chunk<? extends Yorishiro> items) {
    System.out.println("AfterWrite: count=" + items.size());
  }

  @Override
  public void onWriteError(Exception e, Chunk<? extends Yorishiro> items) {
    System.out.println("WriteError: count=" + items.size() + ",error=" + e.getMessage());
  }
}
```

データ書き込み時のリスナー処理を記載します。

# processor\

``` EvaluationProcessor.java
package com.example.demo.processor;

import com.example.demo.domain.model.Yorishiro;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.stereotype.Component;

@Component
@StepScope
public class EvaluationProcessor implements ItemProcessor<Yorishiro, Yorishiro> {

  @Override
  public Yorishiro process(Yorishiro item) {
    try {
      item.evaluationToString();
    } catch (Exception e) {
      System.out.println(e.getLocalizedMessage());
      return null;
    }

    if (item.getAge() < 13 && item.getEvaluation() == 0) {
      System.out.println("憑依魔法：" + item.getName() + "にサンタが憑依しました。");
      System.out.println("生成魔法：サンタが" + item.getName() + "のためにプレゼントを生成しました。");
      System.out.println("憑依解除：サンタは" + item.getName() + "から出ていきました。");
    } else if (item.getEvaluation() == 2) {
      System.out.println("憑依魔法：" + item.getName() + "にサタンが憑依しました。");
      System.out.println("呪術　　：サタンが" + item.getName() + "の寿命を吸い取りました。");
      System.out.println("憑依解除：サタンは" + item.getName() + "から出ていきました。");
    } else {
      System.out.println("憑依魔法：" + item.getName() + "には何も憑依しませんでした。");
    }

    return item;
  }
}
```

メインでやりたい事をprocessorに記載します。
Yorishiroのevaluationが文字列に変換できるかチェックし、できなければreturn null;をしています。nullを返すことで該当のデータは処理がスキップされます。

12歳以下にしているのは自分が12歳ぐらいまではプレゼントもらってた気がするので、とりあえず12歳を上限にしています。-1歳とかはデータ登録時にバリデーションチェックで弾いているので入りません。データ直メンテすれば入るけど、そんなことしてるとサンタさんこないよ。(勝手な大人の都合)

# config\

``` BatchConfig.java
package com.example.demo.config;

import com.example.demo.csv.CsvFooterCallback;
import com.example.demo.csv.CsvHeaderCallback;
import com.example.demo.domain.model.Yorishiro;
import com.example.demo.listener.ExecutionListener;
import com.example.demo.listener.ProcessListener;
import com.example.demo.listener.ReadListener;
import com.example.demo.listener.WriteListener;
import com.example.demo.processor.EvaluationProcessor;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.job.builder.JobBuilder;
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.batch.item.database.JdbcPagingItemReader;
import org.springframework.batch.item.database.builder.JdbcPagingItemReaderBuilder;
import org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean;
import org.springframework.batch.item.file.FlatFileItemWriter;
import org.springframework.batch.item.file.builder.FlatFileItemWriterBuilder;
import org.springframework.batch.item.file.transform.BeanWrapperFieldExtractor;
import org.springframework.batch.item.file.transform.DelimitedLineAggregator;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.batch.BatchDataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.WritableResource;
import org.springframework.core.task.TaskExecutor;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;

@Configuration
public class BatchConfig {

  @Value("${csv.path}") // application.(yml, properties)の値を読み込む
  private String csvPath;

  @Autowired
  private CsvHeaderCallback csvHeaderCallback;

  @Autowired
  private CsvFooterCallback csvFooterCallback;

  @Autowired
  private ReadListener readListener;

  @Autowired
  private ProcessListener processListener;

  @Autowired
  private WriteListener writeListener;

  @Autowired
  private EvaluationProcessor evaluationProcessor;

  @Bean
  @ConfigurationProperties("spring.datasource.h2")
  DataSourceProperties h2Properties() {
    return new DataSourceProperties();
  }

  @Bean
  @ConfigurationProperties("spring.datasource.mysql")
  DataSourceProperties mysqlProperties() {
    return new DataSourceProperties();
  }

  @BatchDataSource // メタ情報を入れるデータソースに記載します
  @Bean
  DataSource h2DataSource() {
    return h2Properties()
            .initializeDataSourceBuilder()
            .build();
  }

  @Primary // メインのデータソースに記載します
  @Bean
  DataSource mysqlDataSource() {
    return mysqlProperties()
            .initializeDataSourceBuilder()
            .build();
  }

  // csvの書き込み処理
  @Bean
  @StepScope
  public FlatFileItemWriter<Yorishiro> csvWriter() {
    WritableResource resource = new FileSystemResource(csvPath);
    DelimitedLineAggregator<Yorishiro> aggregator = new DelimitedLineAggregator<>();
    aggregator.setDelimiter(DelimitedLineTokenizer.DELIMITER_COMMA);

    BeanWrapperFieldExtractor<Yorishiro> extractor = new BeanWrapperFieldExtractor<>();
    extractor.setNames(new String[]{"id", "name", "age", "evaluationString"});
    aggregator.setFieldExtractor(extractor);

    return new FlatFileItemWriterBuilder<Yorishiro>()
            .name("evaluationCsvWriter")
            .resource(resource)
            .append(false) // false:ファイルの置き換え, true:ファイルに追記
            .lineAggregator(aggregator) // 区切り文字
            .headerCallback(csvHeaderCallback)
            .footerCallback(csvFooterCallback)
            .encoding(StandardCharsets.UTF_8.name())
            .build();
  }

  // データ取得のクエリ
  @Bean
  public SqlPagingQueryProviderFactoryBean queryProvider() {
    SqlPagingQueryProviderFactoryBean provider = new SqlPagingQueryProviderFactoryBean();
    provider.setDataSource(mysqlDataSource());
    provider.setSelectClause("select id, name, age, evaluation");
    provider.setFromClause("from yorishiro");
    provider.setSortKey("id");
    return provider;
  }

  // ページングによるデータ取得
  @Bean
  @StepScope
  public JdbcPagingItemReader<Yorishiro> jdbcPagingReader() throws Exception {
    RowMapper<Yorishiro> rowMapper = new BeanPropertyRowMapper<>(Yorishiro.class);
    return new JdbcPagingItemReaderBuilder<Yorishiro>()
            .name("jdbcPagingItemReader")
            .dataSource(mysqlDataSource())
            .queryProvider(queryProvider().getObject())
            .rowMapper(rowMapper) // バインド
            .pageSize(5) // 1回あたりのデータ取得数
            .build();
  }

  // 並列処理
  @Bean
  public TaskExecutor asyncTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(3); // スレッド数
    executor.setMaxPoolSize(3);
    executor.setThreadNamePrefix("task_");
    executor.initialize();
    return executor;
  }

  @Bean
  public Step exportStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception {
    return new StepBuilder("ExportStep", jobRepository)
            .<Yorishiro, Yorishiro>chunk(2, transactionManager) // 2の箇所がコミット数(何件ずつ実行するか)
            .reader(jdbcPagingReader()).listener(readListener)
            .processor(evaluationProcessor).listener(processListener)
            .writer(csvWriter()).listener(writeListener)
//            .taskExecutor(asyncTaskExecutor())
            .build();
  }

  @Bean
  public Job exportJob(JobRepository jobRepository, PlatformTransactionManager transactionManager, ThreadPoolTaskExecutor executor) throws Exception {
    System.setOut(new PrintStream(System.out, true, "UTF-8"));
    return new JobBuilder("ExportJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(exportStep(jobRepository, transactionManager))
            .listener(new ExecutionListener(executor))
            .build();
  }
}
```

Spring BatchがJobを見つけ実行してくれます。
このときにexportStep()が呼ばれます。
exportStep()の.processor(evaluationProcessor)により、メインでやりたい処理が実行されます。
processorを2つ以上使うことも可能です。※gitのimport側のソースに記載

# 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604560/1d4b2ae7-c60b-6220-93af-2c0b76e1f33d.png)

``` yorishiro.csv
ID,名前,年齢,評価
1,ユーザ1,12,良
2,ユーザ2,12,不良
3,ユーザ3,12,最悪
4,ユーザ4,13,良
5,ユーザ5,13,不良
6,ユーザ6,13,最悪
12,ユーザ1234567,0,良
13,ユーザ123456,0,良
16,ユーザ16,99,最悪
17,ユーザ17,100,良
24,ユーザ24,0,良
25,ユーザ25,0,良
26,ユーザ26,0,良
27,ユーザ27,0,良
28,ユーザ28,0,良
29,ユーザ29,0,良
合計=16件
```

```
18:46:14:  ':com.example.demo.DemoApplication.main()' を実行中...

> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE

> Task :com.example.demo.DemoApplication.main()

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.0)

2024-12-25T18:46:16.241+09:00  INFO 42256 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 23.0.1 with PID 42256 (E:\program\java\springbatch\samon-santa\samon-santa\export\demo\build\classes\java\main started by ryuya in E:\program\java\springbatch\samon-santa\samon-santa\export\demo)
2024-12-25T18:46:16.243+09:00  INFO 42256 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
2024-12-25T18:46:16.879+09:00  INFO 42256 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-12-25T18:46:17.033+09:00  INFO 42256 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:santadb user=SA
2024-12-25T18:46:17.035+09:00  INFO 42256 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-12-25T18:46:17.257+09:00  INFO 42256 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.368 seconds (process running for 1.661)
2024-12-25T18:46:17.260+09:00  INFO 42256 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2024-12-25T18:46:17.263+09:00  INFO 42256 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2024-12-25T18:46:17.380+09:00  INFO 42256 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-2 - Added connection com.mysql.cj.jdbc.ConnectionImpl@40c2ce52
2024-12-25T18:46:17.380+09:00  INFO 42256 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2024-12-25T18:46:17.418+09:00  INFO 42256 --- [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=ExportJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
2024-12-25T18:46:17.439+09:00  INFO 42256 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [ExportStep]
BeforeRead:
AfterRead: item=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=null)
BeforeProcess: item=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=null)
憑依魔法：ユーザ1にサンタが憑依しました。
生成魔法：サンタがユーザ1のためにプレゼントを生成しました。
憑依解除：サンタはユーザ1から出ていきました。
AfterProcess: item=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=良),result=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=null)
憑依魔法：ユーザ2には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=不良),result=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=不良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=null)
憑依魔法：ユーザ3にサタンが憑依しました。
呪術　　：サタンがユーザ3の寿命を吸い取りました。
憑依解除：サタンはユーザ3から出ていきました。
AfterProcess: item=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=最悪),result=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=最悪)
BeforeProcess: item=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=null)
憑依魔法：ユーザ4には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=良),result=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=null)
BeforeProcess: item=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=null)
憑依魔法：ユーザ5には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=不良),result=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=不良)
BeforeProcess: item=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=null)
憑依魔法：ユーザ6にサタンが憑依しました。
呪術　　：サタンがユーザ6の寿命を吸い取りました。
憑依解除：サタンはユーザ6から出ていきました。
AfterProcess: item=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=最悪),result=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=最悪)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ1234567にサンタが憑依しました。
生成魔法：サンタがユーザ1234567のためにプレゼントを生成しました。
憑依解除：サンタはユーザ1234567から出ていきました。
AfterProcess: item=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ123456にサンタが憑依しました。
生成魔法：サンタがユーザ123456のためにプレゼントを生成しました。
憑依解除：サンタはユーザ123456から出ていきました。
AfterProcess: item=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=null)
憑依魔法：ユーザ16にサタンが憑依しました。
呪術　　：サタンがユーザ16の寿命を吸い取りました。
憑依解除：サタンはユーザ16から出ていきました。
AfterProcess: item=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=最悪),result=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=最悪)
BeforeProcess: item=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=null)
憑依魔法：ユーザ17には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=良),result=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ24にサンタが憑依しました。
生成魔法：サンタがユーザ24のためにプレゼントを生成しました。
憑依解除：サンタはユーザ24から出ていきました。
AfterProcess: item=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ25にサンタが憑依しました。
生成魔法：サンタがユーザ25のためにプレゼントを生成しました。
憑依解除：サンタはユーザ25から出ていきました。
AfterProcess: item=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ26にサンタが憑依しました。
生成魔法：サンタがユーザ26のためにプレゼントを生成しました。
憑依解除：サンタはユーザ26から出ていきました。
AfterProcess: item=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ27にサンタが憑依しました。
生成魔法：サンタがユーザ27のためにプレゼントを生成しました。
憑依解除：サンタはユーザ27から出ていきました。
AfterProcess: item=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ28にサンタが憑依しました。
生成魔法：サンタがユーザ28のためにプレゼントを生成しました。
憑依解除：サンタはユーザ28から出ていきました。
AfterProcess: item=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ29にサンタが憑依しました。
生成魔法：サンタがユーザ29のためにプレゼントを生成しました。
憑依解除：サンタはユーザ29から出ていきました。
AfterProcess: item=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
2024-12-25T18:46:17.538+09:00  INFO 42256 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [ExportStep] executed in 98ms
2024-12-25T18:46:17.559+09:00  INFO 42256 --- [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=ExportJob]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 131ms
2024-12-25T18:46:17.563+09:00  INFO 42256 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown initiated...
2024-12-25T18:46:17.580+09:00  INFO 42256 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown completed.
2024-12-25T18:46:17.581+09:00  INFO 42256 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-12-25T18:46:17.581+09:00  INFO 42256 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

BUILD SUCCESSFUL in 2s
3 actionable tasks: 1 executed, 2 up-to-date
18:46:17:  ':com.example.demo.DemoApplication.main()' の実行を完了しました。
```

コミット数が2のためreadを2回実行したらprocessを実行。processを2回実行したらwriteを実行という風になっています。

``` BatchConfig.java
  @Bean
  public Step exportStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) throws Exception {
    return new StepBuilder("ExportStep", jobRepository)
            .<Yorishiro, Yorishiro>chunk(2, transactionManager)
            .reader(jdbcPagingReader()).listener(readListener)
            .processor(evaluationProcessor).listener(processListener)
            .writer(csvWriter()).listener(writeListener)
//            .taskExecutor(asyncTaskExecutor())
            .build();
  }
```

taskExecutorの場所がコメントになっているのでコメントを解除し、実行します。
コメントになっていた箇所は並列処理の内容だったため、並列で実行され、順番がばらばらになります。並行処理についてはgitのimport側のソースに記載しています。

``` Yorishiro.csv
ID,名前,年齢,評価
1,ユーザ1,12,良
4,ユーザ4,13,良
3,ユーザ3,12,最悪
5,ユーザ5,13,不良
2,ユーザ2,12,不良
6,ユーザ6,13,最悪
12,ユーザ1234567,0,良
13,ユーザ123456,0,良
16,ユーザ16,99,最悪
17,ユーザ17,100,良
24,ユーザ24,0,良
25,ユーザ25,0,良
26,ユーザ26,0,良
27,ユーザ27,0,良
28,ユーザ28,0,良
29,ユーザ29,0,良
合計=16件
```

```
18:54:01:  ':com.example.demo.DemoApplication.main()' を実行中...

> Task :compileJava
> Task :processResources UP-TO-DATE
> Task :classes

> Task :com.example.demo.DemoApplication.main()

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/

 :: Spring Boot ::                (v3.4.0)

2024-12-25T18:54:02.593+09:00  INFO 30632 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication using Java 23.0.1 with PID 30632 (E:\program\java\springbatch\samon-santa\samon-santa\export\demo\build\classes\java\main started by ryuya in E:\program\java\springbatch\samon-santa\samon-santa\export\demo)
2024-12-25T18:54:02.595+09:00  INFO 30632 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to 1 default profile: "default"
2024-12-25T18:54:03.188+09:00  INFO 30632 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2024-12-25T18:54:03.336+09:00  INFO 30632 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection conn0: url=jdbc:h2:mem:santadb user=SA
2024-12-25T18:54:03.338+09:00  INFO 30632 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2024-12-25T18:54:03.560+09:00  INFO 30632 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.278 seconds (process running for 1.544)
2024-12-25T18:54:03.563+09:00  INFO 30632 --- [           main] o.s.b.a.b.JobLauncherApplicationRunner   : Running default command line with: []
2024-12-25T18:54:03.566+09:00  INFO 30632 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2024-12-25T18:54:03.679+09:00  INFO 30632 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-2 - Added connection com.mysql.cj.jdbc.ConnectionImpl@40ac6b76
2024-12-25T18:54:03.679+09:00  INFO 30632 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2024-12-25T18:54:03.715+09:00  INFO 30632 --- [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=ExportJob]] launched with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}]
2024-12-25T18:54:03.734+09:00  INFO 30632 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [ExportStep]
BeforeRead:
BeforeRead:
AfterRead: item=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=null)
AfterRead: item=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=null)
BeforeRead:
BeforeRead:
BeforeRead:
AfterRead: item=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=null)
AfterRead: item=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=null)
BeforeProcess: item=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=null)
BeforeProcess: item=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=null)
憑依魔法：ユーザ1にサンタが憑依しました。
憑依魔法：ユーザ3にサタンが憑依しました。
生成魔法：サンタがユーザ1のためにプレゼントを生成しました。
呪術　　：サタンがユーザ3の寿命を吸い取りました。
憑依解除：サンタはユーザ1から出ていきました。
憑依解除：サタンはユーザ3から出ていきました。
AfterProcess: item=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=良),result=Yorishiro(id=1, name=ユーザ1, age=12, evaluation=0, evaluationString=良)
AfterProcess: item=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=最悪),result=Yorishiro(id=3, name=ユーザ3, age=12, evaluation=2, evaluationString=最悪)
BeforeProcess: item=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=null)
憑依魔法：ユーザ4には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=良),result=Yorishiro(id=4, name=ユーザ4, age=13, evaluation=0, evaluationString=良)
憑依魔法：ユーザ5には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=不良),result=Yorishiro(id=5, name=ユーザ5, age=13, evaluation=1, evaluationString=不良)
BeforeWrite: count=2
BeforeWrite: count=2
AfterWrite: count=2
AfterWrite: count=2
AfterRead: item=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=null)
BeforeProcess: item=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=null)
憑依魔法：ユーザ2には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=不良),result=Yorishiro(id=2, name=ユーザ2, age=12, evaluation=1, evaluationString=不良)
BeforeProcess: item=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=null)
憑依魔法：ユーザ6にサタンが憑依しました。
呪術　　：サタンがユーザ6の寿命を吸い取りました。
憑依解除：サタンはユーザ6から出ていきました。
AfterProcess: item=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=最悪),result=Yorishiro(id=6, name=ユーザ6, age=13, evaluation=2, evaluationString=最悪)
BeforeWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterWrite: count=2
AfterRead: item=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ1234567にサンタが憑依しました。
生成魔法：サンタがユーザ1234567のためにプレゼントを生成しました。
憑依解除：サンタはユーザ1234567から出ていきました。
AfterProcess: item=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=12, name=ユーザ1234567, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ123456にサンタが憑依しました。
生成魔法：サンタがユーザ123456のためにプレゼントを生成しました。
憑依解除：サンタはユーザ123456から出ていきました。
AfterProcess: item=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=13, name=ユーザ123456, age=0, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=null)
憑依魔法：ユーザ16にサタンが憑依しました。
呪術　　：サタンがユーザ16の寿命を吸い取りました。
憑依解除：サタンはユーザ16から出ていきました。
AfterProcess: item=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=最悪),result=Yorishiro(id=16, name=ユーザ16, age=99, evaluation=2, evaluationString=最悪)
BeforeProcess: item=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=null)
憑依魔法：ユーザ17には何も憑依しませんでした。
AfterProcess: item=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=良),result=Yorishiro(id=17, name=ユーザ17, age=100, evaluation=0, evaluationString=良)
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=null)
BeforeRead:
BeforeProcess: item=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=null)
AfterRead: item=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ24にサンタが憑依しました。
生成魔法：サンタがユーザ24のためにプレゼントを生成しました。
憑依解除：サンタはユーザ24から出ていきました。
BeforeRead:
AfterProcess: item=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=24, name=ユーザ24, age=0, evaluation=0, evaluationString=良)
AfterRead: item=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ26にサンタが憑依しました。
生成魔法：サンタがユーザ26のためにプレゼントを生成しました。
憑依解除：サンタはユーザ26から出ていきました。
AfterProcess: item=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=26, name=ユーザ26, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ27にサンタが憑依しました。
生成魔法：サンタがユーザ27のためにプレゼントを生成しました。
憑依解除：サンタはユーザ27から出ていきました。
AfterProcess: item=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=27, name=ユーザ27, age=0, evaluation=0, evaluationString=良)
憑依魔法：ユーザ25にサンタが憑依しました。
生成魔法：サンタがユーザ25のためにプレゼントを生成しました。
憑依解除：サンタはユーザ25から出ていきました。
AfterProcess: item=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=25, name=ユーザ25, age=0, evaluation=0, evaluationString=良)
BeforeWrite: count=2
BeforeWrite: count=2
AfterWrite: count=2
AfterWrite: count=2
BeforeRead:
AfterRead: item=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=null)
BeforeRead:
AfterRead: item=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=null)
BeforeProcess: item=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ28にサンタが憑依しました。
生成魔法：サンタがユーザ28のためにプレゼントを生成しました。
憑依解除：サンタはユーザ28から出ていきました。
AfterProcess: item=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=28, name=ユーザ28, age=0, evaluation=0, evaluationString=良)
BeforeProcess: item=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=null)
憑依魔法：ユーザ29にサンタが憑依しました。
生成魔法：サンタがユーザ29のためにプレゼントを生成しました。
憑依解除：サンタはユーザ29から出ていきました。
AfterProcess: item=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=良),result=Yorishiro(id=29, name=ユーザ29, age=0, evaluation=0, evaluationString=良)
BeforeRead:
BeforeWrite: count=2
AfterWrite: count=2
BeforeRead:
BeforeRead:
BeforeRead:
BeforeRead:
2024-12-25T18:54:03.822+09:00  INFO 30632 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [ExportStep] executed in 87ms
2024-12-25T18:54:03.846+09:00  INFO 30632 --- [           main] o.s.b.c.l.s.TaskExecutorJobLauncher      : Job: [SimpleJob: [name=ExportJob]] completed with the following parameters: [{'run.id':'{value=1, type=class java.lang.Long, identifying=true}'}] and the following status: [COMPLETED] in 121ms
2024-12-25T18:54:03.850+09:00  INFO 30632 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown initiated...
2024-12-25T18:54:03.860+09:00  INFO 30632 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Shutdown completed.
2024-12-25T18:54:03.861+09:00  INFO 30632 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-12-25T18:54:03.861+09:00  INFO 30632 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

BUILD SUCCESSFUL in 2s
3 actionable tasks: 2 executed, 1 up-to-date
18:54:03:  ':com.example.demo.DemoApplication.main()' の実行を完了しました。
```

# おわり

プレゼントどこ？