この記事はフリュー Advent Calendar 2023の10日目の記事となります。
はじめに
社内で利用するウェブアプリの機能追加として、エクセルファイルを読み込ませて一括処理を行いたいという要望があったので、実装しました。
オンメモリからエクセルファイルを読み込む方法がレアケースなのか、意外にも他に記事として見かけませんでしたので、私が得た情報をアップロードからの流れに沿ってまとめておこうと思います。
ファイルをアップロードする
フロントエンド、バックエンドそれぞれ説明します。
記載しているソースコードに最低限としており、エラー処理などは入っていませんので実際に利用する際には必要に応じてチェックなど行ってください。
環境
以下の環境で動作を確認しています。
ライブラリ | バージョン |
---|---|
Spring Boot | 3.2.0 |
Open JDK | 21 |
JQuery | 3.7.1 |
Apache POI | 5.2.3 |
その他はSpring Bootプロジェクトデフォルトでの環境となっています。
なお、ブラウザはGoogle Chromeのバージョン120で確認しています。
フロントエンド
今回はhtmlとJavaScriptを利用してアップロードを実現していきます。
JavaScriptを利用せずSpringの機能を使いアップロードを実現する方法もありますが、こちらはまた別途紹介したいと思います。
以下はファイル選択および、アップロードボタンを配置してアップロード処理をJavaScriptで記載したhtmlファイルになります。
JavaScriptはMultipartFormデータとしてファイルのアップロードを行います。
Multipartのキー名をバックエンドと合わせる必要があり、ここではupload_file
という文字列を使用しています。
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script type="text/javascript">
$(function() {
$('#uploadButton').on('click', function(event) {
var $file = $('#uploadSelectFileBox');
// FormData オブジェクトを作成
var formData = new FormData();
formData.append('upload_file', $file.prop("files")[0]);
// Ajaxで送信
$.ajax({
// アップロードを処理するAPIへのURL
url: 'file/upload',
// HTTPメソッド
method: 'post',
// レスポンスの形式
dataType: 'json',
// dataに FormDataを指定
data: formData,
// dataの変換をさせない
processData: false,
// contentTypeをfalseにすることで適切なContentTypeを指定してくれる
contentType: false
}).done(function(data, textStatus) {
// 送信成功
// Jsonが返ってきたら以下のようにパースできる
var data_stringify = JSON.stringify(data);
var data_json = JSON.parse(data_stringify);
// Jsonへのアクセスも簡単にできます
console.log(data_json.message);
}).fail(function(XMLHttpRequest, textStatus, errorThrown) {
// 失敗時の処理(エラーメッセージなど)
console.log(textStatus);
});
return false;
});
});
</script>
</head>
<body>
<input type="file" name="file" id="uploadSelectFileBox" accept=".xlsx"/>
<input type="button" id="uploadButton" value="アップロード" />
</body>
</html>
こんな感じに表示されます。
これでアップロードボタンを押すと、ファイルがサーバーへ送信されます。
フロントエンドではバックエンドのfile/upload
を呼び出していますので、バックエンド側でこのAPIを用意する必要があります。
バックエンド
バックエンドのControllerを作ります。
エクセルファイルを読み込むにはApache POI
ライブラリを利用します。
GradleもしくはMavenの設定ファイルに以下の2つのライブラリを追加します。
※バージョン5.2.3での動作確認を行っています
ここで注意したいのはこれらのライブラリは他のライブラリへの依存関係もありますので、ご自身の環境から必要に応じて上記ページのCompile Dependenciesからバージョンの更新や足りないもの確認してください。
コントローラーの処理は以下になります。
こちらも例によってエラー処理などは端折っていますし、DBへの更新処理を行う場合は安全のために入力値の正当性やトランザクション処理を実装してください。
エクセルのセルへのアクセスもサンプルとして記述しています。
package com.upload;
import java.io.ByteArrayInputStream;
import java.util.Date;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("/file")
public class FileUploadController {
private class UploadResponse
{
public int status = 1;
public String message = "success";
}
@PostMapping(value = "/upload")
@ResponseBody
public UploadResponse uploadFile( @RequestParam("upload_file") MultipartFile multipartFile ) throws Exception {
UploadResponse response = new UploadResponse();
// ファイルが空の場合は異常終了
if(multipartFile.isEmpty()){
// 異常終了時の処理
response.status = 0;
response.message = "アップロードされたエクセルファイルが存在しなかった";
return response;
}
//バイナリを取得する
byte[] bytes = multipartFile.getBytes();
//バイナリからストリームへ変換
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
//エクセルをワークブックインスタンスへ
Workbook wb = WorkbookFactory.create(inputStream);
// ほげ1という名前のシートを取得
Sheet sheet = wb.getSheet("ほげ1");
// 1行目取得
Row row = sheet.getRow(0);
// データがある最後のセル番号取得
final int cellNum = row.getLastCellNum();
// セル分ループ
for (int cellIndex = 0; cellIndex < cellNum; ++cellIndex) {
//セル取得
Cell cell = row.getCell(cellIndex);
//セルの値取得
switch (cell.getCellType()) {
case STRING:
String stringVal = cell.getStringCellValue();
// 文字列データの処理
break;
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date dateVal = cell.getDateCellValue();
// 日付データの処理
} else {
double numVal = cell.getNumericCellValue();
// 数値データの処理
}
break;
default:
// その他のデータタイプに対する処理
break;
}
//必要に応じた処理
}
response.message = "正常に完了しました";
return response;
}
}
実装は以上となります。
ポイントとしては、JavaScript側で指定したupload_file
を指定してアップロードされたファイルを取得している点と、WorkbookFactory.createへはbyte[]では引数に渡せなかったのでストリームとしてByteArrayInputStream
を使ってエクセルを開いています。
セルにアクセスするにはセルに設定されている値の種類を取得し、種類によって処理を振り分ける必要があります。
エクセルの操作内容については以下のように他に詳しい記事がたくさんありますので、そちら参考にしていただくのが良いかと思います。
まとめ
・シンプルなアップロード処理は簡単に実現できます
・ByteArrayInputStream
を使えばオンメモリでエクセルファイルを開くことができる
・Apache POI
を使えば簡単にエクセル操作が可能になります
ライブラリがあるおかげで実装量も少なくアップロードからエクセル操作まで実装できました。
次回、Spring BootとThymeleafを利用したアップロード方法も紹介したいと思います。