Spring BatchでCSVを読み込むときにフィールドの値をゼロ埋めする

こんにちは、さるまりんです。

Excelでデータを作ってCSVにする。この時にゼロ始まりの文字列からゼロが落ちてしまうことってありませんか?
これでおかしなことが起こってしまったのでその回避方法を記しておきます。

CSVの時刻を表すフィールドに71657の文字列がありました。時刻なので07165707:16:57の意味です。
が、プログラムの中でTime型(java.sql.Time)に変換する時になぜか00:05:07になってました。
なぜ?
この値、71657ミリ秒として時刻に変換すると00:05:07になります。

プログラムで見るとこんな感じです。

long ms = 71657;
Time t = new Time(ms);
System.out.println(t);

↓が出力されます。

00:05:07

あら、思ってもなかった誤変換です。

こんなことが起きないようにFieldSetMapperをカスタマイズしてゼロ埋めしてから処理するようにします。

流れとしては

  1. FlatFileItemReaderで文字列のまま取得
  2. FieldSetMapperで桁数を直す (6桁にゼロ埋め)
  3. ItemProcessorでTimeに変換

です。

データを読み込むエンティティクラスMyEntityはこんな感じです。

import java.sql.Time;

public class MyEntity {
    private String rawTime;  // 読み込み時の文字列(ゼロ埋め済み)
    private Time time;       // Time 型に変換後

    // Getter & Setter
    public String getRawTime() {
        return rawTime;
    }

    public void setRawTime(String rawTime) {
        this.rawTime = rawTime;
    }

    public Time getTime() {
        return time;
    }

    public void setTime(Time time) {
        this.time = time;
    }
}

やってみます。

1. FieldSetmapperで6桁にゼロ埋めする

まず、FlatFileItemReaderFieldSetMapperをカスタマイズして、CSVを読み込む時に71657071657に変換します。

import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
import org.springframework.validation.BindException;

public class CustomFieldSetMapper implements FieldSetMapper<MyEntity> {
    @Override
    public MyEntity mapFieldSet(FieldSet fieldSet) throws BindException {
        MyEntity item = new MyEntity();
        
        // CSV から読み込んだ文字列
        String rawTime = fieldSet.readString("timeColumn");

        // 6桁にゼロ埋め
        String paddedTime = String.format("%06d", Integer.parseInt(rawTime));

        item.setRawTime(paddedTime); // ここでは String のまま
        return item;
    }
}

2. FlatFileItemReaderCustomFieldSetMapperを適用する

@Bean
public FlatFileItemReader<MyEntity> reader() {
    FlatFileItemReader<MyEntity> reader = new FlatFileItemReader<>();
    reader.setResource(new ClassPathResource("data.csv"));
    reader.setLinesToSkip(1); // ヘッダーをスキップ
    reader.setLineMapper(new DefaultLineMapper<MyEntity>() {{
        setLineTokenizer(new DelimitedLineTokenizer() {{
            setNames("id", "timeColumn"); // CSV のカラム名
        }});
        setFieldSetMapper(new CustomFieldSetMapper()); // カスタムマッパー適用
    }});
    return reader;
}

CustomFieldSetMapperを使ってゼロ埋めした文字列をMyEntityrawTime071657に渡しています。

3. ItemProcessorTimeに変換する

ItemProcessorを実装し、rawTime(071657)をTime型(java.sql.Time)に変換します。

import org.springframework.batch.item.ItemProcessor;
import java.sql.Time;

public class TimeFormatProcessor implements ItemProcessor<MyEntity, MyEntity> {
    @Override
    public MyEntity process(MyEntity item) throws Exception {
        // "071657" を Time オブジェクトに変換
        Time formattedTime = convertToTime(item.getRawTime());
        item.setTime(formattedTime); // Time 型のフィールドにセット
        return item;
    }

    private Time convertToTime(String paddedTime) {
        int hours = Integer.parseInt(paddedTime.substring(0, 2));
        int minutes = Integer.parseInt(paddedTime.substring(2, 4));
        int seconds = Integer.parseInt(paddedTime.substring(4, 6));

        return Time.valueOf(String.format("%02d:%02d:%02d", hours, minutes, seconds));
    }
}

4. JobConfigurationItemProcessorを適用する

@Bean
public ItemProcessor<MyEntity, MyEntity> processor() {
    return new TimeFormatProcessor();
}

これで71657は07:16:57に、915は00:09:15に、3150は00:30:15に正しく変換してくれます。

読み込みに失敗してエラーで止まってくれたら気づきやすいのですが、知らぬ間に変換されて進んでしまうと気づけない。

テストってとても大切なんですね。

発生しそうなことを色々と想定して安全なプログラムを作っていきたいと思います。

読んでくださってありがとうございました。

それではまた!