スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Spring MVC ファイルアップロードを実装する[DB登録編]

今回は、ファイルアップロード機能を実装してみる。

対応すべき内容が多いので、記事を3回に分けて掲載する。
 ①[DB登録編]Book情報の作成にあたって、タイトル、価格に加えて画像ファイルもDBに登録する
  ⇒本記事
 ②[DB参照編]登録した画像ファイルを画面に表示させる
  ⇒次回掲載予定
 ③[入力チェック編]ファイルサイズに上限を設け、オーバーした場合は入力チェックエラーとする
  ⇒次々回掲載予定

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

Spring MVCでファイルアップロード機能を適用するために、MultipartResolverのBean定義が必要となる。
MultipartResolverインターフェースを実装しているクラスとして、
 ①StandardServletMultipartResolver
 ②CommonsMultipartResolver
が挙げられる。①②どちらのパターンでもファイルアップロードができることを確認したが、今回はServlt3.0で使用可能な①StandardServletMultipartResolverをメインとする。

1.Mavenのpom.xml
①StandardServletMultipartResolverの場合



javax.servlet
javax.servlet-api
3.0.1
provided


commons-io
commons-io
2.2


commons-ioについては、InputStreamをバイト配列に変換するIOUtilsクラスを使用するために定義している。

②CommonsMultipartResolverの場合(参考)



javax.servlet
javax.servlet-api
3.0.1
provided


commons-fileupload
commons-fileupload
1.3.1


CommonsMultipartResolverの場合、内部でcommons-fileuploadを使用しているため必須となる。
また、commons-fileuploadはcommons-ioに依存しているため、commons-ioを別途定義する必要はない。

2.mvc-config.xml(関連箇所のみ掲載)
①StandardServletMultipartResolverの場合


<bean id="multipartResolver"
class="org.springframework.web.multipart.support.StandardServletMultipartResolver"/>


②CommonsMultipartResolverの場合(参考)

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

補足として、CommonsMultipartResolverはファイルの最大サイズを設定できるmaxUploadSizeプロパティなど用意されている。

3.web.xml(関連箇所のみ掲載)



dispatcherServlet
org.springframework.web.servlet.DispatcherServlet

contextConfigLocation
/WEB-INF/mvc-config.xml

1

C:\\tmp



dispatcherServlet
/


multipart-configタグの「location」にて、一時ファイルの生成有無を確認しやすくするため、出力先を変更している。
他にmultipart-configタグで定義できる要素として、「max-file-size」「max-request-size」「file-size-threshold」などがある。
「max-file-size」や「max-request-size」は、ファイル、リクエストの最大サイズを指定するのだが、これらの設定値(例:50000※単位:バイト)を超えたファイルをアップロードした場合、「org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is java.lang. ・・・(略)・・・: the request was rejected because its size (100000) exceeds the configured maximum (50000)」のような実行時例外が発生する。
今回は例外処理ではなく、入力チェックエラーとするため、「location」以外は定義しない。

4.newBook.jsp


<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<link href="<c:url value="/css/bootstrap.min.css" />" rel="stylesheet">
<link href="<c:url value="/css/bootstrap-theme.min.css" />" rel="stylesheet">
<script src="<c:url value="/js/bootstrap.min.js" />"></script>

<title>newBook</title>
</head>
<body>
<spring:message code="bookName" var="varBookName"/>
<spring:message code="price" var="varPrice"/>
<spring:message code="cover" var="varCover"/>

<div class="container-fluid">
<form:form
action="${pageContext.request.contextPath}/book/create"
method="post" modelAttribute="bookForm" class="well" enctype="multipart/form-data">
<div class="form-group">
<form:label path="bookName" class="control-label">${varBookName}</form:label>
<form:input path="bookName" class="form-control"/>
<p class="help-block"><font color="red"><form:errors path="bookName" /></font></p>
</div>
<div class="form-group">
<form:label path="price" class="control-label">${varPrice}</form:label>
<form:input path="price" class="form-control"/>
<p class="help-block"><font color="red"><form:errors path="price" /></font></p>
</div>
<div class="form-group">
<label for="uploadFile" class="control-label">${varCover}</label>
<input name="uploadFile" type="file" class="form-control"/>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary"/>
</div>
</form:form>
</div>
</body>
</html>

まず、form:formタグに「enctype="multipart/form-data"」を追加している。また、ファイルアップロードの部品として、inputタグでtype属性を「file」としている。 name属性(値は任意) は「uploadFile」としており、この値をリクエストパラメータとしてコントローラ側で受け取るよう対応する。
補足として${varCover}は国際化対応により、日本語と英語でラベルを切り替えるようにしている。ただし、残念ながらファイルアップロード部品の「参照」ボタンは、日本語のブラウザ環境では切り替わらない。

■前回の国際化対応の記事
Spring MVC メッセージの国際化に対応する

5.BookForm.java


package jp.co.sample.book.form;

import java.io.Serializable;
import javax.servlet.http.Part;
import javax.validation.constraints.Min;
import org.hibernate.validator.constraints.NotEmpty;

public class BookForm implements Serializable {

private static final long serialVersionUID = 1L;

private String bookId;

@NotEmpty
private String bookName;

@Min(1)
private String price;

private byte[] cover;

//getter・setterは省略

アップロードしたファイルを画面に表示するために、BookFormオブジェクトにバイト配列のcover項目を用意する。

6.BookEntity.java


package jp.co.sample.book.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="Book")
public class BookEntity implements Serializable{

private static final long serialVersionUID = 1L;

@Id
@Column(name="book_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private long bookId;

@Column(name="book_name", length=50, nullable=false)
private String bookName;

@Column(nullable=false)
private int price;

private byte[] cover;

//getter・setterは省略

エンティティにもバイト配列のcover項目を用意する。
Bookテーブル側にはBLOB型の同名項目を用意するため、エンティティとマッピングするための@Columnアノテーションは不要。
参考までにBookテーブルのCreate文を掲載しておく。
CREATE TABLE Book (
book_id BIGINT PRIMARY KEY AUTO_INCREMENT,
book_name VARCHAR(50) NOT NULL,
price INTEGER,
cover BLOB
);

7.BookController.java


package jp.co.sample.book.controller;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import javax.validation.Valid;

import jp.co.sample.book.entity.BookEntity;
import jp.co.sample.book.form.BookForm;
import jp.co.sample.book.form.BookSearchForm;
import jp.co.sample.book.service.BookService;
import jp.co.sample.util.BookUtil;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

@Controller
@RequestMapping("/book")
public class BookController {

private static Logger logger = LoggerFactory.getLogger(BookController.class);

@Autowired
protected BookService<BookEntity> bookService;

@RequestMapping(method = RequestMethod.GET)
public String index(BookForm form) {
logger.info("index start");
return "newBook";
}

@RequestMapping(value = "/list", method = RequestMethod.GET)
public String list(BookSearchForm form, Model model) {
logger.info("list start");
List<BookEntity> entites = bookService.findAll();
model.addAttribute("books", entites);
return "list";
}

@RequestMapping(value = "/list/search", method = RequestMethod.GET)
public String search(BookSearchForm form, Model model) {
logger.info("search start");
List<BookEntity> entites = bookService.findByNameLike(form.getBookName());
model.addAttribute("books", entites);
return "list";
}

@RequestMapping(value = "/create", method = RequestMethod.POST)
public String create(@Valid BookForm form, BindingResult result, Model model,
@RequestParam(value="uploadFile",required=false) MultipartFile file) {

logger.info("create start");
if (result.hasErrors()) {
return "newBook";
}

if (!file.isEmpty()) {
byte[] fileContent = null;
InputStream is = null;
try {
is = file.getInputStream();
fileContent = IOUtils.toByteArray(is);
form.setCover(fileContent);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
}
}

BookEntity entity = BookUtil.copyProperties(form);
bookService.addEntity(entity);
model.addAttribute("book", entity);
return "result";
}
}

コントローラクラスを全掲載したが、今回ファイルアップロード機能を追加するのはcreateメソッドになる。
@RequestParamアノテーションのvalue属性に「uploadFile」として、先ほど「4.newBook.jsp」で説明した「uploadFile」と一致させる。今回ファイルアップロードの項目は必須としないため、required属性はfalseとしておく。(デフォルト値はtrue。falseにしないと、入力がない場合、実行時例外が発生。)参考までに、型をMultipartFileとしているが、javax.servlet.http.Partでもよい。(Partにした場合、使えるメソッドは変わってくるが。)
それでは処理の説明に入る。
まず、file.isEmpty()メソッド。値がnullの場合trueとなるが、ファイルサイズが0の場合もtrueとなるため、空ファイルの判定もできる。
file.getInputStream()メソッドでInputStreamを取得し、org.apache.commons.io.IOUtilsクラスを使って、InputStreamをバイト配列に変換している。バイト配列に変換した値をBookFormオブジェクトに格納する。finallyにて、InputStreamをクローズする。IOUtilsを使うと、finally内でtry~catchを記述することなく、シンプルに記述できる。
あとは今までと同じく、BookFormの情報をエンティティにコピーして、addEntityメソッドでDBに登録している。ちなみに、過去の記事でJPAとJDBCそれぞれで登録するパターンを掲載したが、今回はJPAを使用している。
最後に細かな変更点として、@RequestMappingアノテーションのvalue値の先頭に、パスであることが分かるよう「/」を付与するようにした。

■Spring Data JPAに関する過去の記事
Spring MVC Spring Data JPAを使って登録する

8.登録画面

ファイルアップロード

[DB登録編]はここまで。次回、DBに登録したバイナリのアップロードファイル情報を復元して、画面表示するまでをやってみる。


■過去のSpring関連記事
Spring関連記事 Index

スポンサーサイト

コメントの投稿

非公開コメント

プロフィール

bookmount8

Author:bookmount8
システムエンジニア。サーバーサイドでjavaを扱うことが多い。最近は、ミドルやフロント周りも関心あり。

最新記事
カテゴリ
検索フォーム
最新コメント
月別アーカイブ
これまでの訪問者数
ブロとも申請フォーム

この人とブロともになる

RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。