jXLS Readerを使ってアップロードされたExcelファイルを読み込む
先日、jXLSを使ったExcelテンプレートをSpring MVCのビューとして利用する方法 - 達人プログラマーを目指してでjXLSを使ってExcelファイルを生成してダウンロードする方法について説明しました。ここでは逆に、アップロードされたExcelファイルを読み込んでPojoに変換する方法について説明します。
Sring MVCのファイルアップロードサポートを利用する
Spring MVCでファイルアップロード機能を利用するためには、まず、commons-fileuploadとcommons-ioへの依存を追加する必要があります。
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.0.1</version> </dependency>
さらに、Spring MVCのBean定義ファイルに以下を追加します。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- one of the properties available; the maximum file size in bytes --> <property name="maxUploadSize" value="100000" /> </bean>
以上の設定について、詳しくは以下の説明を参考にしてください。
15. Web MVC framework
jXLS Readerを利用してExcelファイルを自動的にPojoに読み込む
次に、jXLSの拡張機能であるjXLS Readerを依存関係ライブラリーとして追加します。
<dependency> <groupId>net.sf.jxls</groupId> <artifactId>jxls-reader</artifactId> <version>1.0-RC-1</version> </dependency>
jXLS Readerについては、
JXLS -
に簡単な説明がありますが、以下のようなxmlファイルでExcelファイルを読み込むセルの場所を指定しておくことで、自動的に読み込んだExcelの値をPojoにバインドしてくれます。*1
<?xml version="1.0" encoding="utf-8"?> <workbook> <worksheet name="Sheet1"> <section startRow="0" endRow="6"> <mapping cell="B1">department.name</mapping> <mapping cell="A4">department.chief.name</mapping> <mapping cell="B4">department.chief.age</mapping> <mapping cell="D4">department.chief.payment</mapping> <mapping row="3" col="4">department.chief.bonus</mapping> </section> <loop startRow="7" endRow="7" items="department.staff" var="employee" varType="com.github.ryoasai.spring_jxls.example.Employee"> <section startRow="7" endRow="7"> <mapping row="7" col="0">employee.name</mapping> <mapping row="7" col="1">employee.age</mapping> <mapping row="7" col="3">employee.payment</mapping> <mapping row="7" col="4">employee.bonus</mapping> </section> <loopbreakcondition> <rowcheck offset="0"> <cellcheck offset="0">Employee Payment Totals:</cellcheck> </rowcheck> </loopbreakcondition> </loop> </worksheet> </workbook>
ここで、私がはまった点を書いておくと、jXLS Readerには現状以下の制約(バグ?)があるようです。(開発チーム報告して直してもらいたいと思っていますが。)
- たとえ、バインドする値がない場合でもsectionの領域は先頭行から定義しなくてはならない。(必要なら空のsectionを定義する)
- loopbreakconditionで空セルのチェックがうまくいかない。上記の例のようにセルに値が入っている場合は問題ありません。
特に、後者の問題は普通ループの終端を空セルで行いたいことが多いのでちょっと不便です。OffsetCellCheckImplクラスの以下のメソッドでnullと空文字の比較がtrueとならないことが原因と思われます。(nullと空文字の比較をtrueと判定すべき)
public boolean isCheckSuccessful(Cell cell) { Object obj = getCellValue(cell, value); if (value == null) { return obj == null; } else { return value.equals(obj); } }
次に、コントローラーから汎用的に利用できる以下のヘルパークラスを作成します。
package com.github.ryoasai.spring_jxsl; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; import net.sf.jxls.reader.ReaderBuilder; import net.sf.jxls.reader.XLSReadStatus; import net.sf.jxls.reader.XLSReader; import org.apache.commons.io.IOUtils; import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.springframework.core.io.Resource; import org.springframework.ui.Model; import org.springframework.web.multipart.MultipartFile; import org.xml.sax.SAXException; public class JxlsFileReader { public XLSReadStatus read(Resource templateFile, MultipartFile file, Model model) throws InvalidFormatException, IOException, SAXException { return read(templateFile, file, model.asMap()); } public XLSReadStatus read(Resource templateFile, MultipartFile file, Map<?, ?> model) throws InvalidFormatException, IOException, SAXException { InputStream inputXML = null; InputStream inputXLS = null; try { inputXML = new BufferedInputStream(templateFile.getInputStream()); XLSReader mainReader = ReaderBuilder.buildFromXML(inputXML); inputXLS = new BufferedInputStream(file.getInputStream()); return mainReader.read(inputXLS, model); } finally { IOUtils.closeQuietly(inputXML); IOUtils.closeQuietly(inputXLS); } } }
次に、このクラスをBeanとしてDIコンテナに登録します。
<bean id="jxlsFileReader" class="com.github.ryoasai.spring_jxsl.JxlsFileReader" />
最後に、アップロードボタンに対応するコントローラーのメソッドを以下のように実装します。
@Inject JxlsFileReader jxlsFileReader; ... private Resource uploadFileTemplate = new ClassPathResource( "department.xml", getClass()); @RequestMapping(value = "/upload", method = RequestMethod.POST) public String handleFormUpload(@RequestParam("file") MultipartFile file, Model model) throws InvalidFormatException, IOException, SAXException { // バインドしておきたいPojoをモデルにつめておく。 Department department = new Department(); model.addAttribute(department); jxlsFileReader.read(uploadFileTemplate, file, model); System.out.println(department); System.out.println(department.getChief()); System.out.println(department.getStaff()); return "redirect:/"; }
ちょっと準備は必要でしたが、このような仕組みを利用することで、ループ処理など低水準の処理をほとんどコーディングすることなく、Excelファイルの読み込みを行うことができます。
なお、サンプルのファイルは前回と同様にGitHub - ryoasai/spring-mvc-exts: Some extensions to the Spring MVC web application framework.に登録してありますので、ご利用ください。