JavaのCSVライブラリ「opencsv」

業務でJavaのCSVライブラリを探して使用したので、備忘録としてまとめていきます。
#JavaのCSVライブラリの一覧(2018.01時点)
JavaでCSVライブラリは下記のものがあります。

+ opencsv  http://opencsv.sourceforge.net/
+ OrangeSignalCSV http://orangesignal.github.io/orangesignal-csv/ ※2014年から更新が止まっています
+ Super CSV http://super-csv.github.io/super-csv/index.html ※2015年から更新が止まっています 
+ Apache Commons CSV https://commons.apache.org/proper/commons-csv/

今回はopencsvを使用しました。
#環境
+ Java 8
+ Gradle 4.1
+ opencsv 4.1

opencsv 4 はjava7以降が対応です。
また、opencsv3.xとopencsv4.xでは使い方が異なりますので注意してください。

#やりたいこと
こんな内容のCSVファイル(item.csv)の読取と出力を行います。

```csv:item.csv
"商品ID","商品名","単価","数量"
001,"ポテトチップス",150,50
002,"チョコレート",100,30
003,"アイス",120,10
```
ポイントは3点です。

+ ヘッダーつき(日本語)
+ セパレータはカンマ
+ 文字列はダブルクォート「"」でくくる

#準備
Gradleのdependencyにopencsvを追加します。
commons-loggingをexcludeしている場合はコメントアウトしてください。実行時に例外が発生します。

```gradle:build.gradle
    //下記の記載がある場合はコメントアウトが必要
   configurations {
        //all*.exclude module: 'commons-logging'
   }

    dependencies {
        compileOnly group:'org.projectlombok', name:'lombok', version:'1.16.20'
        //csv library
        implementation group:'com.opencsv', name:'opencsv', version:'4.1'
        implementation group:'commons-logging', name: 'commons-logging', version:'1.2'
    }
```
また、CSVファイルの項目に合わせたEntityクラスを用意します。
Entityクラスのフィールドにアノテーションを付加することで、EntityクラスとCSVファイルのカラムを紐付けます。

アノテーションは取り込みパターンに応じて2種類から選択できます。

+ `@CsvBindByName` ヘッダーカラムとフィールドを紐付け
+ `@CsvBindByPosition` 入力順とフィールドを紐付け(ヘッダーつきのため今回は除外します)

`@CsvBindByName`を使用する場合

```java:ItemEntity.java
package entity;

import com.opencsv.bean.CsvBindByName;

import lombok.Data;

/**
 * 商品を管理するEntityクラス。
 */
@Data
public class ItemEntity {

	@CsvBindByName(column = "商品ID", required = true)
	private String id;

	@CsvBindByName(column = "商品名", required = true)
	private String name;

	@CsvBindByName(column = "単価", required = true)
	private int price;

	@CsvBindByName(column = "数量", required = true)
	private int amount;
}
```

`@CsvBindByPosition`を使用する場合

```java:ItemEntity2.java
package entity;

import com.opencsv.bean.CsvBindByPosition;

import lombok.Data;

/**
 * 商品を管理するEntityクラス。
 */
@Data
public class ItemEntity2 {

	@CsvBindByPosition(position = 0, required = true)
	private String id;

	@CsvBindByPosition(position = 1, required = true)
	private String name;

	@CsvBindByPosition(position = 2, required = true)
	private int price;

	@CsvBindByPosition(position = 3, required = true)
	private int amount;
}
```

#CSV読み取り
CSV読み取りのコードサンプルです。

```java:ItemFileDao.java
CsvToBean<ItemEntity> csvToBean = new CsvToBeanBuilder<ItemEntity>(reader).withType(ItemEntity.class).build();
List<ItemEntity> items = csvToBean.parse();
```
[CsvToBeanBuilder](http://opencsv.sourceforge.net/apidocs/com/opencsv/bean/CsvToBeanBuilder.html) クラスを使用します。
`CsvToBeanBuilder#withType` メソッドまたは `CsvToBeanBuilder#withMappingStrategy` メソッドを必ず呼び出してください。
セパレータを指定する場合は  `CsvToBeanBuilder#withSeparator` メソッドを使用してください。

コードサンプルでは `@CsvBindByName` を使用したEntityクラスを使用しています。
 `@CsvBindByPosition` を使用したEntityクラスの場合、ヘッダーがなければ同様の結果が得られます。

#CSV出力
CSV出力のコードサンプルです。

```java:ItemFileDao.java
StatefulBeanToCsv<ItemEntity> beanToCsv = new StatefulBeanToCsvBuilder<ItemEntity>(writer).build();
beanToCsv.write(beans);
```
[StatefulBeanToCsvBuilder](http://opencsv.sourceforge.net/apidocs/com/opencsv/bean/StatefulBeanToCsvBuilder.html) クラスを使用します。
セパレータを指定する場合は  `CsvToBeanBuilder#withSeparator` メソッドを使用してください。

`@CsvBindByName` を使用したEntityクラスを使用した場合、ヘッダーは自動で付加されますが、カラムの順番はヘッダーの文字コード順(?)となるようです。
`@CsvBindByPosition` を使用したEntityクラスを使用した場合、ヘッダーは自動で付加されません。

#サンプルコードと実行結果

```java:ItemFileDao.java

import java.io.Reader;
import java.io.Writer;
import java.util.List;

import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
import com.opencsv.bean.StatefulBeanToCsv;
import com.opencsv.bean.StatefulBeanToCsvBuilder;
import com.opencsv.exceptions.CsvException;

/**
 * CSVファイルとのやり取りを行うDaoクラス。
 */
public class ItemFileDao {

	public void write(Writer writer, List<ItemEntity> beans) throws CsvException {
		StatefulBeanToCsv<ItemEntity> beanToCsv = new StatefulBeanToCsvBuilder<ItemEntity>(writer).build();
		beanToCsv.write(beans);
	}
	public List<ItemEntity> read(Reader reader) throws CsvException {
		CsvToBean<ItemEntity> csvToBean = new CsvToBeanBuilder<ItemEntity>(reader).withType(ItemEntity.class).build();
		return csvToBean.parse();
	}
}
```


```java:Main.java
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import com.opencsv.exceptions.CsvException;

public class Main {

	public static void main(String[] args) {

		ItemFileDao csvFileDao = new ItemFileDao();
		
		try (Reader reader = Files.newBufferedReader(Paths.get("item.csv"));
				Writer writer = Files.newBufferedWriter(Paths.get("item2.csv"));) {
			List<ItemEntity> items = csvFileDao.read(reader);
			items.forEach(System.out::println);
			csvFileDao.write(writer, items);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (CsvException e) {
			e.printStackTrace();
		}
	}
}
```

```
ItemEntity(id=001, name=ポテトチップス, price=150, amount=50)
ItemEntity(id=002, name=チョコレート, price=100, amount=30)
ItemEntity(id=003, name=アイス, price=120, amount=10)
```

```csv:item2.csv
"単価","商品ID","商品名","数量"
"150","001","ポテトチップス","50"
"100","002","チョコレート","30"
"120","003","アイス","10"
```