SpringBootでLocalDateTimeを含んだデータをCSV形式で出力する方法
SpringBootを使って、データ内にLocalDateTime型のフィールドがある場合のCSVファイル作成方法です。
CSVファイルを作成して、そのファイルをダウンロードするのではなく、CSV文字列を返すイメージです。
ブラウザでGETリクエストをするとダウンロードできるようにしてみました。
依存関係を追加する
依存関係をgradleで追加します。
dependencies { compile( //https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-csv 'com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.2.3', //https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 // jacksonでjava8のdate型を使うため 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.7', )
jackson-dataformat-csv
はcsvをモデルクラスから作るためのもの。
jackson-datatype-jsr310
はjava8のDateがモデル内にあった場合に必要になるもの。
資料参照しつつ試したこと
csvデータの作成
github(jackson-dateformats-csv)の使用方法を見ました。 https://github.com/FasterXML/jackson-dataformats-text/tree/master/csv
ドキュメントの中間あたりに以下のコードがあったのでこれを参考にしてます。
CsvMapper mapper = new CsvMapper(); Pojo value = ...; CsvSchema schema = mapper.schemaFor(Pojo.class); // schema from 'Pojo' definition String csv = mapper.writer(schema).writeValueAsString(value);
こんなことを考えました。
- PojoクラスはModelクラスみたいなものだろう。たぶんgetterとかsetterがあれば良いはず。
- CsvMapperはObjectMapperみたいなものだろう。継承してるし、configureの設定とかできそう
- 最後にschemaで設定したクラスをもとにして、valueをcsv形式の文字列にしてるのかな
結果として、動きませんでした。
その時のエラーがこちらです。
com.fasterxml.jackson.core.JsonGenerationException: CSV generator does not support Object values for properties at com.fasterxml.jackson.core.JsonGenerator._reportError(JsonGenerator.java:1961) at com.fasterxml.jackson.dataformat.csv.CsvGenerator.writeStartObject(CsvGenerator.java:327) at com.fasterxml.jackson.core.base.GeneratorBase.writeStartObject(GeneratorBase.java:286) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:151) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:727) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:719)
なにかのパラメータが原因でcsvが作れてないのかな?と考えましたが、わからないので片っ端から調べました。
どうやらLocalDateTimeが原因らしいというのがわかったので、解決方法を調べました。
以下のコードを追加することで解消できました。
このコードはstackoverflowから見つけたものです。(タブを消してしまって、もう一度検索して見つけることができませんでした。orz)
JavaTimeModule javaTimeModule = new JavaTimeModule(); javaTimeModule.addDeserializer( LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ISO_DATE_TIME) ); csvMapper.registerModule(javaTimeModule);
おそらく、マッピングするさいにjava8のLocalDateTimeに対応していないマッパーを、ISO_DATE_TIMEで文字列に変換できるようにしているのだと思います。
上記のコードではなく、Modelクラスに@DateTimeFormat
を追加して対応してみましたが、結果は変わりませんでした。
ダウンロードの設定
headerと、ファイル名を指定していご、レスポンスを返しています。
HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "text/csv;"); String filename = "hoge"; headers.setContentDispositionFormData("filename", filename + ".csv"); return new ResponseEntity<byte[]>(csv.getBytes(), headers, HttpStatus.OK);
完成コード
※ 一つのControllerに処理を書いていますが、一般的にはServiceクラスに分けた方が良いです。
@ResponseBody @RequestMapping(value = "/download/csv", method = RequestMethod.GET) public Object downloadCsv() { CsvMapper csvMapper = new CsvMapper(); csvMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); CsvSchema schema = csvMapper.schemaFor(DataModel.class).withHeader(); JavaTimeModule javaTimeModule = new JavaTimeModule(); // Hack time module to allow 'Z' at the end of string (i.e. javascript json's) javaTimeModule.addDeserializer( LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ISO_DATE_TIME) ); csvMapper.registerModule(javaTimeModule); // ↓DBからデータをセレクト List<DataModel> dataList = service.selectAll(); String csv = null; try { csv = csvMapper.writer(schema).writeValueAsString(dataList); } catch (JsonProcessingException e) { // 本来はExceptionとか出します。 e.printStackTrace(); } HttpHeaders headers = new HttpHeaders(); headers.add("Content-Type", "text/csv;"); String filename = "hoge"; headers.setContentDispositionFormData("filename", filename + ".csv"); return new ResponseEntity<>(csv.getBytes(), headers, HttpStatus.OK); }