CSVファイル操作

CSVファイル操作

CSVファイルからデータの取り込む方法と、CSVファイルへの書き込みを学習します。
CSVファイルとは?使い方やExcelファイルとの違いについて解説 | アララ メッセージ

CSVの基本として、以下の内容は最低限押さえておきましょう。
1. CSV=テキストファイル
2. 行ごとに1つのレコードを表す
3. 列はカンマで区切られている

準備(CSVファイル、テーブルなど)

CSVファイル

下記のCSVファイルを用意します。

CSVファイルはサクラエディタで確認するようにしましょう。
下記内容を入力して .csv拡張子のファイルとして保存してください。

サクラエディタ等のテキストエディタの他、Excel等の表計算ソフトでも扱うことが出来ます。(作成・編集・閲覧)
下図のように入力して .csv 形式で保存することでも同様のファイルを作成可能です。

テーブル、エンティティ、リポジトリー

・CSVから読み込んだデータを保存するために、以下のテーブルを用意してください。
idは自動でセットされるとして、その他のカラムを取込対象とします。

「csvs」テーブル

カラムデフォルト値
idINT自動採番
codeVARCHARNULL
nameVARCHARNULL
ageINTNULL
addressVARCHARNULL

・テーブルに合わせたEntity、Repositoryも用意します。

フォーム送信されたCSVファイルを読み込んでDB登録

CSVファイルフォーム送信~読み込み、DB登録

View

このViewでは、画面上部にCSVアップロード用のフォームがあり、
その下に処理が完了した場合に表示する完了メッセージ・登録データの一覧表示を記述しています。
【/src/main/resources/templates/csv/index.html】

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
	<meta content="text/html" charset="UTF-8">
	<title>CSVファイル操作</title>
	<link rel="stylesheet" th:href="@{/css/manual.css}">
</head>
<body><!-- 「CSVファイル操作」 -->
	<h1>CSVアップロード・出力画面</h1>
	<div th:if="${resultMessage}" class="messageBlock"><!-- 完了メッセージ -->
		<p th:text="${resultMessage}" class="messageBlock_text"></p>
	</div>
	<div class="block">
		<h2>CSVアップロード</h2>
		<form method="POST" th:action="@{/csv/upload}" enctype="multipart/form-data">
			<input type="file" name="file">
			<input type="submit" value="アップロード">
			<th:block th:if="${csvForm}" th:object="${csvForm}"><!-- エラー表示 -->
				<p th:if="${#fields.hasErrors('file')}" th:errors="*{file}" style="color:red"></p>
			</th:block>
		</form>
		<th:block th:if="${list}"><!-- アップロード処理した場合、その内容を表示する -->
			<div class="result-block" th:if="${list.size() > 0}">
				<h3>DBに保存したデータ</h3>
				<table>
					<tr>
						<th>code</th>
						<th>name</th>
						<th>age</th>
						<th>address</th>
					</tr>
					<tr th:each="data : ${list}" th:object="${data}">
						<td th:text="*{code}"></td>
						<td th:text="*{name}"></td>
						<td th:text="*{age}"></td>
						<td th:text="*{address}"></td>
					</tr>
				</table>
			</div>
		</th:block>
	</div>
</body>

Controller(ここではServiceクラスを省略し簡単に記述)

ファイルを読み込むために必要なクラスをいくつか活用していますが、説明はコード内のコメントに記載していますので参照してください。
全体の流れとしては「CSVファイルを読み込み、1行ずつ処理してDBに登録」です。
その他、ヘッダーの項目数チェックなども組み込んでいます。
【/com/cmps/spring/controller/CsvController.java】

package com.cmps.spring.controller;

import java.io.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.cmps.spring.entity.Csv;
import com.cmps.spring.form.CsvForm;
import com.cmps.spring.repository.CsvRepository;

@RequestMapping("/csv")
@Controller
public class CsvController {

	@Autowired
	CsvRepository csvRepository;

	//CSVファイルヘッダー
	private static final String[] FILE_HEADER = { "code", "name", "age", "address" };

	/**
	 * フォーム初期表示画面
	 * @param model Model
	 * @return
	 */
	@GetMapping("")
	public String index(Model model,
			@ModelAttribute("resultMessage") String resultMessage,
			@ModelAttribute("list") LinkedList<Csv> list) {
		
		return "csv/index";
	}

	/**
	 * CSVファイルをアップロード・DB登録機能
	 * @param model
	 * @param form
	 * @param result
	 * @param redirectAttributes
	 * @return
	 */
	@PostMapping("/upload")
	public String upload(Model model,
			@Validated CsvForm form, BindingResult result,
			RedirectAttributes redirectAttributes) {

		// エラー処理
	        if (result.hasErrors()) {
        	    return index(model, csvForm, "", new LinkedList<Csv>());
	        }

		//取り込んだCSVファイル
		MultipartFile file = form.getFile();
		//保存したデータをviewに表示するためのList
		List<Csv> list = new LinkedList<Csv>();

		//ファイルに対し処理を行うために各クラスを使用、検査例外のためtryブロックの中で実行
		//InputStream :ファイル内容を読み込むためのクラス
		//InputStreamReader :バイト・ストリームから文字ストリームへの橋渡しの役目。文字にデコードする。
		//BufferedReader :テキストを効率よく読み込むためのクラス
		try (InputStream inputStream = file.getInputStream();
				BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {

			//読み取ったCSVの行を入れるための変数を作成
			String line;

			//ヘッダーレコードを読み取り
			//BufferedReaderのreadLine()メソッド:1行分読み出しする。
			//次にreadLine()を使用する際には次行を読み出しする(使うたびに次行に進むイメージ)
			line = br.readLine();

			//Stringのsplitメソッドを使用してカンマごとに分割して配列に入れる
			String[] tmpHeaderSplit = line.split(",");
			//ヘッダーが正しいかチェック
			if (!Arrays.equals(tmpHeaderSplit, FILE_HEADER)) {
				//一致しない場合は、BindingResultに登録しエラーとしてアップロードViewに返す
				result.rejectValue("file", "", "ヘッダー項目が一致しません。");
				return "csv/index";
			}

			//データ全行取得:行がNULL(CSVの値がなくなる)になるまで処理を繰り返す
			while ((line = br.readLine()) != null) {
				//splitメソッドを使用してカンマごとに分割して配列にいれる
				String[] csvSplit = line.split(",");
				//項目数チェック
				if (csvSplit.length != FILE_HEADER.length) {
					result.rejectValue("file", "", "項目数が一致しません。");
					return "csv/index";
				}

				//Entity作成、データ保存
				Csv csvData = new Csv(csvSplit[0], csvSplit[1], Integer.parseInt(csvSplit[2]), csvSplit[3]);
				csvRepository.save(csvData);
				
				//リストに追加(View表示用)
				list.add(csvData);
			}
			br.close();

		} catch (IOException e) {
			e.printStackTrace();
			result.rejectValue("file", "", "ファイル読み込みに失敗しました");
			return "csv/index";
		}
		
		// redirectAttributesに登録
		redirectAttributes.addFlashAttribute("resultMessage", "CSVファイルのアップロードが完了しました。");
		redirectAttributes.addFlashAttribute("list", list);
		return "redirect:/csv";
	}
}

Form

【/com/cmps/spring/form/CsvForm.java】

package com.cmps.spring.form;

import java.io.Serializable;
import org.springframework.web.multipart.MultipartFile;
import lombok.Data;

@Data
public class CsvForm implements Serializable {
	
	//CSVファイル
	@FileSize
	@FileNotEmpty
	private MultipartFile file;
}


アップロード画面にアクセスし、用意したcsvファイルをフォームからアップロードするとDBに登録ができることを確認してみましょう。

DBからデータ内容を取得し、CSV形式で出力

jackson-dataformat-csvライブラリ

CSVファイルの出力書き込みを行うには、jackson-dataformat-csvライブラリが活用できます。
FasterXML/jackson-dataformats-text: Uber-project for (some) standard Jackson textual format backends: csv, properties, yaml (xml to be added in future)

プロジェクト直下のpom.xml<dependencies>タグ内に記述してください。

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-csv</artifactId>
</dependency>

入力が完了したら、プロジェクトを更新します。
(Spring Bootアプリケーションを実行中の場合は終了させてから、プロジェクト名を右クリック>Maven>プロジェクトの更新 をクリック)

プロジェクト内にCSVファイルを出力

View

CSVファイルを出力するためのフォームと、後ほど使用するCSVファイルのダウンロード用のフォームを含みます。
【/src/main/resources/templates/csv/index.html】のbody閉じタグ前に追記

	<div class="block">
		<h2>CSVファイルをプロジェクト内に出力</h2>
		<form method="POST" th:action="@{/csv/output}">
			<input type="submit" value="出力">
		</form>
		<form method="POST" th:action="@{/csv/output-download}">
			<input type="submit" value="出力済みのCSVをダウンロード">
		</form>
	</div>

Controller

jackson-dataformat-csvライブラリのCsvMapperCsvSchemaクラス等を活用します。
CSVファイルへの書き込みはBufferedWriterクラスを活用して書き込みを実行します。
説明はコード内のコメントに記載していますので参照してください。
【/com/cmps/spring/controller/CsvController.java】に追記

package com.cmps.spring.controller;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.cmps.spring.dto.CsvDto;
import com.cmps.spring.service.CsvService;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;

@RequestMapping("/csv")
@Controller
public class CsvController {

	@Autowired
	CsvService csvService;

	/**
	 * テーブル全件をCSVファイルとしてプロジェクト内のフォルダに出力する処理
	 * @param redirectAttributes
	 * @return
	 * @throws JsonProcessingException
	 */
	@PostMapping("/output")
	public String output(RedirectAttributes redirectAttributes) {

		//CSVに出力したいデータ全件のListの取得
		List<CsvDto> csvList = csvService.getCsvList();

		//CsvMapper :CsvSchemaインスタンスを生成する拡張機能を備えたObjectMapperの一種(JSONを読み書き/変換する機能を持つ)
		CsvMapper mapper = new CsvMapper();
		//CsvSchema :CSVファイルを読み書きするためのクラス
		//DtoのJson~アノテーションからヘッダーを作成してくれる
		CsvSchema schema = mapper.schemaFor(CsvDto.class).withHeader();

		//書き込み対象ファイル
		Path path = Path.of("src/main/resources/static/csv/sample.csv");
		Resource resource = new PathResource(path);

		try {
			//ファイルが存在しない場合、ファイルを作成する
			if(!resource.exists()) {
				Files.createFile(path);
			}

			//BufferedWriterによる書き込み準備
			//newBufferedWriterの第3引数は省略可。省略時はStandardOpenOption.TRUNCATE_EXISTINGと同じ、上書き処理になる。APPENDだと追記
			BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8,
					StandardOpenOption.TRUNCATE_EXISTING);

			//CSVのデータをファイルに出力
			mapper.writer(schema).writeValues(writer).writeAll(csvList);

		} catch (Exception e) {
			e.printStackTrace();
			redirectAttributes.addFlashAttribute("resultMessage", "CSVファイルの出力に失敗しました。");
			return "redirect:/csv";
		}

		redirectAttributes.addFlashAttribute("resultMessage", "CSVファイルの出力が完了しました。");
		return "redirect:/csv";
	}
}

Service

Controllerから一部処理を分離しています。
createDownloadHeaders()メソッドは後ほどダウンロードの章で使用されます。

【/com/cmps/spring/service/CsvService.java】

package com.cmps.spring.service;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.ResponseEntity.BodyBuilder;
import org.springframework.stereotype.Service;

import com.cmps.spring.dto.CsvDto;
import com.cmps.spring.entity.Csv;
import com.cmps.spring.repository.CsvRepository;

@Service
public class CsvService {

	@Autowired
	CsvRepository csvRepository;

	
	/**
	 * csvsテーブル全件のList<CsvDto>を取得
	 * CsvMapperを活用するために@Json~アノテーションを付与したDTOクラスが必要
	 * 
	 * @return List<CsvDto>
	 */
	public List<CsvDto> getCsvList() {
		//最終的に使用するDTOクラス
		List<CsvDto> csvList = new LinkedList<>();

		//DBから全件取得し、CsvDtoのリストに詰め直し
		List<Csv> allData = csvRepository.findAll();
		for (Csv data : allData) {
			CsvDto csvDto = new CsvDto(data.getCode(), data.getName(), data.getAge(), data.getAddress());
			csvList.add(csvDto);
		}
		return csvList;
	}

	/**
	 * ダウンロード用のHttpHeadersを生成するヘルパーメソッド
	 * 
	 * @param filename  ファイル名
	 * @param mediaType メディアタイプ
	 * @return HttpHeaders
	 * @throws UnsupportedEncodingException
	 */
	public HttpHeaders createDownloadHeaders(String filename, MediaType mediaType) throws UnsupportedEncodingException {
	    // HTTPリクエストおよびレスポンスのヘッダーを設定するクラスHttpHeadersのインスタンス化
	    HttpHeaders headers = new HttpHeaders();
	
	    // ファイル名に対しURLエンコーディングを行う。
	    // 日本語などの非ASCII文字が含まれている場合、正しく処理されるようにエンコードする必要がある
	    String encodedFilename = URLEncoder.encode(filename, StandardCharsets.UTF_8.toString());
	
	    // HTTPレスポンスのボディをどのように扱うべきかをクライアント(ブラウザなど)に指示するために設定
	    // attachment; は、ブラウザにレスポンスボディをWebページとして表示するのではなく、ダウンロードして保存するように指示
	    // filename="... は、ダウンロードされるファイルのデフォルト名を指定
	    headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedFilename + "\"");
	    // レスポンスボディに含まれるコンテンツのメディアタイプ(MIMEタイプ)をクライアントに伝えるためのヘッダー
	    headers.setContentType(mediaType);
	
	    return headers;
	}
}

DTO

CsvMapperを使用してCSVファイルを生成するにあたり、DTOクラスを作成します。(DTO=Data Transfer Object)
また、CsvMapperの機能の関係上 必要になるので、Entityにも@NoArgsConstructorを追加してください。【/com/cmps/spring/dto/CsvDto.java】

package com.cmps.spring.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;
import lombok.NoArgsConstructor;

@JsonPropertyOrder({ "code", "name", "age", "address" }) //@JsonPropertyOrderを付けることで、csvファイルに出力される順番が確実に保証される
@NoArgsConstructor //引数なしコンストラクタ、CsvMapperによるMappingのために必要
@Data
public class CsvDto {
	// ID IDも足したい場合は同様に追加可能
	//@JsonProperty("code")
	//private Integer id;

	// コード
	@JsonProperty("code") //@JsonPropertyで出力したいCSVファイルのカラム名を指定する
	private String code;

	// 名前
	@JsonProperty("name")
	private String name;

	// 年齢
	@JsonProperty("age")
	private Integer age;

	// アドレス
	@JsonProperty("address")
	private String address;

	// 引数ありコンストラクタ
	public CsvDto(String code, String name, Integer age, String address) {
		this.code = code;
		this.name = name;
		this.age = age;
		this.address = address;
	}
}

指定したパスのファイルsample.csvが生成されることを確認しましょう。

プロジェクト内のCSVファイルをダウンロードさせる

Controller

ファイルをダウンロードさせるには、ResponseEntity (Spring Framework API) – Javadocを使用します。
対象のファイル・ダウンロード時のファイル名を指定して、ServiceからcreateDownloadHeadersメソッドを呼び出し、HttpHeadersを取得。
このHttpHeadersを用いてResponseEntityを生成し返却しています。
【/com/cmps/spring/controller/CsvController.java】に追記

@RequestMapping("/csv")
@Controller
public class CsvController {

	/**
	 * プロジェクト内のCSVファイルをダウンロードさせる処理
	 * 
	 * @return ResponseEntity<Resource> CSVダウンロードの実行
	 * @throws UnsupportedEncodingException
	 */
	@PostMapping("/output-download")
	@ResponseBody // RestControllerのようにWebページではなく値やオブジェクトなどを返すメソッドに対して付与する
	public ResponseEntity<Resource> outputFileDownload() throws UnsupportedEncodingException {
	
	    // ダウンロード対象ファイル
	    Path path = Path.of("src/main/resources/static/csv/sample.csv");
	    Resource resource = new PathResource(path);
	
	    // ファイル名
	    String filename = "CSVファイル出力.csv";
	
	    // HttpHeadersインスタンスを取得、ResponseEntityを返却(CSVファイルをダウンロード)
	    HttpHeaders headers = csvService.createDownloadHeaders(filename, new MediaType("text", "csv"));
	    // ResponseEntity(第1引数から順にボディ、ヘッダー、ステータスコード)
	    return new ResponseEntity<>(resource, headers, HttpStatus.OK);	
	}
}

HTTPヘッダー (HttpHeaders)とは、HTTP通信における封筒に書かれた情報のようなものです。データ本体(封筒の中身)を送る際に、そのデータに関するさまざまな情報をヘッダーに含めます。
具体的には、以下のような情報です。(以下は一部)
・Content-Type: データの中身がどんな形式か(例:テキスト、画像、PDFなど)。受け取った側がデータをどう扱えばいいか判断するために使用される。
・Content-Disposition: ファイルをダウンロードさせるか、ブラウザで表示させるかといった、データの扱い方を指示する。

レスポンスエンティティ (ResponseEntity)とは、サーバーからクライアントへ返される返信用のパッケージ全体を表現するものです。手紙を送った相手から返事が来るとき、単に手紙の中身だけではなく、以下のような情報も付いてきます。
1. 封筒(ヘッダー): 差出人の情報や、どんな内容の返事かを示す情報。
2. 手紙の中身(ボディ): 伝えたいデータ本体。
3. 返事の状態(ステータスコード): 返事が成功したのか(例:「OK」)、失敗したのか(例:「見つからない」)を示す。

実際に操作して挙動を確認してみましょう。

1つのメソッド内でCSVファイルを生成・ダウンロードさせる

上記2つの例では、あくまで「プロジェクト内にCSVファイルを作成・保存」してから、「プロジェクト内のCSVファイルをダウンロードさせる」といった流れでした。
もし、中継なしで1メソッドでCSVファイル生成し、そのまま即時ダウンロードさせたい場合は下記のように実装できます。

View

【/src/main/resources/templates/csv/index.html】のbody閉じタグ前に追記

	<div class="block">
		<h2>現状のテーブル全件をCSVでダウンロード</h2>
		<form method="POST" th:action="@{/csv/download}">
			<input type="submit" value="ダウンロード">
		</form>
	</div>

Controller

	/**
	 * テーブル全件をCSVファイルにしてダウンロードさせる処理
	 * 
	 * @return ResponseEntity<byte[]> CSVダウンロードの実行
	 * @throws JsonProcessingException
	 * @throws UnsupportedEncodingException
	 */
	@PostMapping("/download")
	@ResponseBody
	public ResponseEntity<byte[]> download() throws JsonProcessingException, UnsupportedEncodingException {
	
	    // データ全件のListの取得
	    List<CsvDto> csvList = csvService.getCsvList();
	
	    // CsvMapper :CsvSchemaインスタンスを生成する拡張機能を備えたObjectMapperの一種(JSONを読み書き/変換する機能を持つ)
	    CsvMapper mapper = new CsvMapper();
	    // 文字列にダブルクオートをつけたい場合、下記を有効にする
	    // mapper.configure(CsvGenerator.Feature.ALWAYS_QUOTE_STRINGS, true);
	    // CsvSchema :CSVファイルを読み書きするためのクラス
	    CsvSchema schema = mapper.schemaFor(CsvDto.class).withHeader();
	
	    // CSVのテキスト内容を作成+後の処理のためバイト配列化
	    byte[] csvTextToBytes = mapper.writer(schema).writeValueAsString(csvList).getBytes();
	
	    // ファイル名
	    String filename = "CSVファイル出力2.csv";
	
	    // HttpHeadersインスタンスを取得、ResponseEntityを返却
	    HttpHeaders headers = csvService.createDownloadHeaders(filename, new MediaType("text", "csv"));
	    return new ResponseEntity<>(csvTextToBytes, headers, HttpStatus.OK);
	}

参考サイト:
取り込み
【初心者向け】SPRINGでCSVデータをDBに登録してみた #Java – Qiita
SpringBootで、CSVを読み込んでList<String>に変換する – Qiita

プロジェクト内に出力
Jackson Text Dataformats Moduleを使って、CSVファイルの読み書きをしてみる – CLOVER

CSVを生成してダウンロード
Spring Boot + ThymeleafでCSVファイルをダウンロードする
テキストファイルをダウンロードする(Java, SpringBoot) – Qiita

練習問題

問1: 以下のバリデーションをFormクラスに追加してください。
・ファイルが添付されていることを確認する
・ファイルサイズが100kB以下であることを確認する
・拡張子チェック

問2: フォームにテキストの入力欄(inputのtype=”text”)を追加し、入力された文字列の名称でCSVファイルがダウンロードされるように実装してください。(「あいうえお」と入力された場合、「あいうえお.csv」)

練習問題のヒント
問1
テキストアップロードの単元の内容を用います。

タイトルとURLをコピーしました