竹形誠司 ブログ
Java+MySQL+Tomcat    »トピック一覧
掲示板へのスパムが多いため、「ご質問」のコーナーはユーザー登録制とさせていただきました。お手数ですが、上の「新規ユーザーの登録」メニューより登録をお願いします。
帳票Web
アプリケーション

受注開発始めました
詳しくは こちら
竹形 誠司 著/ラトルズ刊
JSP帳票アプリケーション実践開発入門
JSP帳票アプリケーション
実践開発入門

JSP業務アプリケーション短期開発入門
JSP業務アプリケーション
短期開発入門

Java+MySQL+Tomcatで始めるWebアプリケーション構築入門
Java+MySQL+Tomcatで始めるWebアプリケーション構築入門

Java+MySQL+Tomcatで作る掲示板とブログ
Java+MySQL+Tomcatで作る
掲示板とブログ
MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
SQL文を文字列で書かなければならないので、バイナリデータをSQLデータベースに入れるのはムリなんじゃないか、と思っていませんか?それができるんです。使うのはプリペアード ステートメントとBlob型。Blobというのは、Binary Large OBjectを表す略語です。

たとえば、次のようなテーブルがあったとします。

create table blob_test(
    id INT auto_increment primary key,
    title VARCHAR(255),
    file_name VARCHAR(255),
    data MEDIUMBLOB
);
dataカラムに指定しているMEDIUMBLOBは、約16Mバイトまでのバイナリデータを格納することができます。データを送信する側のフォームはこんな感じです。

<%@ page contentType="text/html; charset=Windows-31J"%>
<HTML>
<HEAD>
<TITLE>BLOB UPLOAD TEST</TITLE>
</HEAD>
<BODY>
<FORM ACTION ="BlobUploadProcess.jsp" METHOD="post" ENCTYPE="MULTIPART/FORM-DATA">
タイトル<INPUT TYPE="text" NAME="title"><BR>
ファイル<INPUT TYPE="file" NAME="file"><BR>
<INPUT TYPE="submit">
</FORM>
</BODY>
</HTML>
データを受信するコードではApache commonsのFileUploadを使います。
こんな感じです。

<%@ page contentType="text/html; charset=Windows-31J"%>
<%@ page import="java.sql.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="org.apache.commons.fileupload.disk.*"%>
<%@ page import="org.apache.commons.fileupload.servlet.*"%>
<%
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
String strTitle=null;
String strFileName=null;
InputStream stream=null;
for(FileItem item:items) {
    if (item.isFormField()) {    //フォームのフィールド
        String strFieldName = item.getFieldName();
        if(strFieldName.equals("title")){
            strTitle = item.getString("Windows-31J");
        }
    } else {    //ファイル転送
        if(item.getString().equals("")){    //ファイル添付なし
        }else{
            strFileName=item.getName();
            int intPBackslash = strFileName.lastIndexOf("\\");
            if(intPBackslash!=-1){
                strFileName = strFileName.substring(intPBackslash+1);
            }
            stream = item.getInputStream();
        }
    }
}

Class.forName("com.mysql.jdbc.Driver");
String strConn = "jdbc:mysql://localhost/test"
    +"?user=Mulder&password=TrustNo1"
    +"&useUnicode=true&characterEncoding=Windows-31J";
Connection conn = DriverManager.getConnection(strConn);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO blob_test set title=?,file_name=?,data=?");
pstmt.setString(1,strTitle);
pstmt.setString(2,strFileName);
pstmt.setBlob(3,stream);
pstmt.executeUpdate();
conn.close();
%>
<HTML><HEAD><TITLE>BLOB UPLOAD TEST</TITLE></HEAD>
<BODY>
<P>アップロード完了</P>
<P><a HREF="BlobList.jsp">一覧</a></P>
</BODY>
</HTML>
FileUploadのFileItemオブジェクトからgetInputStream()メソッドでInputStreamを取得できます。これをPreparedStatementオブジェクトのsetBlobメソッドに指定すればよいわけです。上のコードでは、アップロードできるファイルをブラウザで表示する画像に制限するため、拡張子がjpg、gif、pngのいずれかであるかどうかをチェックしています。

MySQLに格納したバイナリデータをブラウザに返すにはサーブレットを使います。

package jp.veltec.test;

import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.*;
import java.sql.*;

public class FileDownload extends HttpServlet {

    public void doGet(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException{
        String strId = request.getParameter("id");
        String strConn = "jdbc:mysql://localhost/test"
            +"?user=Mulder&password=TrustNo1"
            +"&useUnicode=true&characterEncoding=utf-8";
        Blob data = null;
        InputStream is = null;
        String strFileName = null;
        try{
            Class.forName("com.mysql.jdbc.Driver");
            Connection conn = DriverManager.getConnection(strConn);
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery(
              "SELECT file_name,data FROM blob_test WHERE id = "
              + strId);
            if(rs.next()){
                data = rs.getBlob("data");
                strFileName = rs.getString("file_name");
            }else{
                throw new IllegalArgumentException("ファイルがありません");
            }
            is = data.getBinaryStream();
            conn.close();
        }catch(SQLException e){
            throw new ServletException(e.getMessage());
        }catch(ClassNotFoundException e){
            throw new ServletException(e.getMessage());
        }
        ServletOutputStream output = response.getOutputStream();
        if(strFileName.endsWith(".jpg")){
            response.setContentType("image/jpg");
        }else if(strFileName.endsWith(".gif")){
            response.setContentType("image/gif");
        }else if(strFileName.endsWith(".png")){
            response.setContentType("image/png");
        }else{
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition"," attachment; filename=\"" + strFileName +"\"");
        }
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[10000];
        while(true){
            int length = is.read(buffer);
            if(length==-1){
                break;
            }
            baos.write(buffer,0,length);
        }
        baos.writeTo(output);
        output.flush();
    }
}
上のコードでは、ResultSetオブジェクトのgetBlobメソッドでBlob型のオブジェクトを取得し、更にgetBinaryStreamメソッドでInputStreamを取得しています。その後、whileループで、InputStreamからデータを読み込んでByteArrayOutputStreamに書き出しています。

FileUploadの使い方やサーバからブラウザにデータを転送するサーブレットについては、拙著「Java+MySQL+Tomcatで作る掲示板とブログ」にも書いたので、こちらも参考にしてください。

画像をデータベースに入れておくと、ファイル名の重複による上書きを気にしなくてよかったり、ユーザーの権限によってアクセスコントロールができたりと、いろいろ便利ですが、オーバーヘッドがどれぐらいあるかはちょっと気になりますね。

----------------------
追記:テストに使用したjarファイルのバージョンは以下のとおりです。
commons-fileupload-1.2.1.jar
commons-io-1.4.jar
投稿:竹形 誠司[takegata]/2008年 01月 15日 22時 21分 /更新:2009年 02月 22日 00時 26分
バックアップの取り方
by 竹形 誠司[takegata]
バイナリデータを含むBLOBカラムは、そのままではmysqldumpでバックアップが取れません。この場合は次のようにmysqldumpで、hex-blobオプションを指定します。

shell> mysqldump --hex-blob -u root -p test > test.sql
投稿:竹形 誠司[takegata]/2008年 02月 06日 12時 19分 /更新:2008年 02月 06日 12時 20分
PreparedStatement の setBlob
by 竹形 誠司[takegata]
PreparedStatement の setBlobメソッドにInputStreamを直接渡せるのは、J2SE6.0からです。J2SE5.0ではBlob型(java.sqlパッケージで定義されている)のオブジェクトを渡す必要があります。

Mac OSX のJava 6 が早く欲しいところですね。
投稿:竹形 誠司[takegata]/2008年 02月 06日 14時 20分 /更新:2008年 02月 06日 14時 38分
setBlobではなく、setBytes
by 竹形 誠司[takegata]
setBlobではなく、setBytesでいけるかも知れません。こんな感じです。

pstmt.setBytes(3, item.get());
今、ちょっと時間がなくて試せませんが、これがOKならJ2SE5.0でも大丈夫ですね。

情報元:
http://okwave.jp/qa3455885.html

投稿:竹形 誠司[takegata]/2008年 02月 09日 15時 45分 /更新:2008年 02月 09日 16時 15分
RE:setBlobではなく、setBytes
by 竹形 誠司[takegata]
上のコードでは変数 「item」 がforループの中でしか使えないので、エラーになってしまいますね。「InputStreamの代わりにbyte[]を使う」ということなので、BlobUploadProcess.jspは次のようになります。
<%@ page contentType="text/html; charset=Windows-31J"%>
<%@ page import="java.sql.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="org.apache.commons.fileupload.disk.*"%>
<%@ page import="org.apache.commons.fileupload.servlet.*"%>
<%
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
String strTitle=null;
String strFileName=null;
//InputStream stream=null;
byte[] aryBytesFile = null;//←★
for(FileItem item:items) {
    if (item.isFormField()) {    //フォームのフィールド
        String strFieldName = item.getFieldName();
        if(strFieldName.equals("title")){
            strTitle = item.getString("Windows-31J");
        }
    } else {    //ファイル転送
        if(item.getString().equals("")){    //ファイル添付なし
        }else{
            strFileName=item.getName();
            int intPBackslash = strFileName.lastIndexOf("\\");
            if(intPBackslash!=-1){
                strFileName = strFileName.substring(intPBackslash+1);
            }
            //stream = item.getInputStream();
            aryBytesFile = item.get();//←★
        }
    }
}

Class.forName("com.mysql.jdbc.Driver");
String strConn = "jdbc:mysql://localhost/test"
    +"?user=Mulder&password=TrustNo1"
    +"&useUnicode=true&characterEncoding=utf-8";
Connection conn = DriverManager.getConnection(strConn);
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO blob_test set title=?,file_name=?,data=?");
pstmt.setString(1,strTitle);
pstmt.setString(2,strFileName);
//pstmt.setBlob(3,stream);
pstmt.setBytes(3,aryBytesFile);//←★
pstmt.executeUpdate();
conn.close();
%>
<HTML><HEAD><TITLE>BLOB UPLOAD TEST</TITLE></HEAD>
<BODY>
<P>アップロード完了</P>
<P><a HREF="BlobList.jsp">一覧</a></P>
</BODY>
</HTML>

投稿:竹形 誠司[takegata]/2008年 08月 10日 16時 58分 /更新:2008年 08月 10日 17時 01分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
BlobList.jspはこんな感じです。大したコードではありませんが、一応載せておきます。

<%@ page contentType="text/html; charset=Windows-31J"%>
<%@ page import="java.sql.*"%>
<%@ page import="jp.veltec.test.*"%>
<%
Class.forName("com.mysql.jdbc.Driver");
String strConn = "jdbc:mysql://localhost/test"
    +"?user=Mulder&password=TrustNo1"
    +"&useUnicode=true&characterEncoding=utf-8";
Connection conn = DriverManager.getConnection(strConn);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT id,title,file_name FROM blob_test");
%>
<HTML>
BLOB UPLOAD TEST<BR>
<%while(rs.next()){%>
<%=rs.getInt("id")%>:
<a HREF="FileDownload?id=<%=rs.getInt("id")%>"><%=rs.getString("title")%></a>:
<BR>
<%}%>

<a HREF="BlobUploadForm.jsp">アップロード</a><BR>
</FORM>
</HTML>
投稿:竹形 誠司[takegata]/2009年 02月 20日 21時 34分 /更新:2009年 02月 20日 21時 40分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様
ありがとうございました。
一覧開けました。
すみませんが、ワード文書、エクセル、PDFをアップロードダウンロードしたいのですが、FileDownload.javaのコンテントタイプはどのように設定すればよいのでしょうか。
BlobUpload.jspを以下のように修正してみたのですが、
if(item.getString().equals("")){    //ファイル添付なし
        }else{
            strFileName=item.getName();
            if(!strFileName.endsWith(".jpg") &&
            !strFileName.endsWith(".gif") &&
            !strFileName.endsWith(".pdf") &&
            !strFileName.endsWith(".xls") &&
            !strFileName.endsWith(".txt") &&
            !strFileName.endsWith(".doc") &&
            !strFileName.endsWith(".png") ){response.setContentType("image/png")
ダウンロードのファイルダウンロードのコンテントタイプの設定が分からず壊れたファイルになってしまいます。
ご教示おねがいします。
投稿:スワジランド[swajiland]/2009年 02月 20日 23時 45分 /更新:2009年 02月 20日 23時 45分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様
何度もすみません。
jpgデータをアップロードしたところ、以下のようなエラーで落ちました。
org.apache.jasper.JasperException: An exception occurred processing JSP page /BlobUploadProcess.jsp at line 52

49: pstmt.setString(1,strTitle);
50: pstmt.setString(2,strFileName);
51: pstmt.setBlob(3,stream);
52: pstmt.executeUpdate();
53: conn.close();
54: %>
55: <HTML><HEAD><TITLE>BLOB UPLOAD TEST</TITLE></HEAD>


Stacktrace:
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:505)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:398)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


原因

javax.servlet.ServletException: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (2249702 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.
    org.apache.jasper.runtime.PageContextImpl.doHandlePageException(PageContextImpl.java:852)
    org.apache.jasper.runtime.PageContextImpl.handlePageException(PageContextImpl.java:781)
    org.apache.jsp.BlobUploadProcess_jsp._jspService(BlobUploadProcess_jsp.java:126)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


原因

com.mysql.jdbc.PacketTooBigException: Packet for query is too large (2249702 > 1048576). You can change this value on the server by setting the max_allowed_packet' variable.
    com.mysql.jdbc.MysqlIO.send(MysqlIO.java:3202)
    com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1932)
    com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2101)
    com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2554)
    com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1761)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2046)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1964)
    com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1949)
    org.apache.jsp.BlobUploadProcess_jsp._jspService(BlobUploadProcess_jsp.java:110)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


Packet for query is too large (2249702 > 1048576). だそうで
You can change this value on the server by setting the max_allowed_packet' variable
とあるのですが、どこの設定をかえればよいのでしょうかご教示おねがいします。
投稿:スワジランド[swajiland]/2009年 02月 21日 01時 01分 /更新:2009年 02月 21日 01時 02分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
原因の所に
com.mysql.jdbc.PacketTooBigException:
と書いてあるので、これはMySQLの設定ですね。設定ファイル(Windowsであれば、my.ini)のmysqldセクションに次のような行を入れて、MySQLを再起動してみてください。
max_allowed_packet=10M
これで10Mバイトまでのファイルが登録できるようになると思います。
投稿:竹形 誠司[takegata]/2009年 02月 21日 11時 44分 /更新:2009年 02月 21日 12時 06分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
上のサーブレットの次の部分
        if(strFileName.endsWith(".jpg")){
            response.setContentType("image/jpg");
        }else if(strFileName.endsWith(".gif")){
            response.setContentType("image/gif");
        }else if(strFileName.endsWith(".png")){
            response.setContentType("image/png");
        }else{
            response.setContentType("application/octet-stream");
        }
はファイル名の拡張子を調べてContentTypeを設定していますが、jpg、gif、png以外の場合は"application/octet-stream"を設定しています。.pdfや.docもここに入るので、ダウンロードのダイアログボックスが表示されるはずです。

次のような行を上のブロックに挿入すると、PDFファイルを選んだときにダウンロードのダイアログボックスではなく、Adobeリーダーを起動してPDFファイルの内容を表示することができます。
        }else if(strFileName.endsWith(".pdf")){
            response.setContentType("application/pdf");
WordやExcelの場合も同様ですが、一般のアプリケーションの場合は一度ダウンロードしてから開いてもらった方が良いのではないでしょうか。
投稿:竹形 誠司[takegata]/2009年 02月 21日 12時 05分 /更新:2009年 02月 21日 12時 05分
ファイルサイズの制限
by 竹形 誠司[takegata]
BlobUploadProcess.jspでファイルサイズを制限するようにしてみました。

<%@ page contentType="text/html; charset=Windows-31J"%>
<%@ page import="java.sql.*"%>
<%@ page import="java.util.*"%>
<%@ page import="java.io.*"%>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page import="org.apache.commons.fileupload.disk.*"%>
<%@ page import="org.apache.commons.fileupload.servlet.*"%>
<%@ page import="org.apache.commons.fileupload.FileUploadBase.*"%>
<%
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
String strTitle=null;
String strFileName=null;
String strErrorMessage=null;
upload.setSizeMax(1000000);//1M byte ←★
try{ //←★
  List<FileItem> items = upload.parseRequest(request);
  //InputStream stream=null;
  byte[] aryBytesFile = null;
  for(FileItem item:items) {
      if (item.isFormField()) {  //フォームのフィールド
        String strFieldName = item.getFieldName();
      if(strFieldName.equals("title")){
        strTitle = item.getString("Windows-31J");
      }
      } else {  //ファイル転送
      if(item.getString().equals("")){  //ファイル添付なし
      }else{
        strFileName=item.getName();
        int intPBackslash = strFileName.lastIndexOf("\\");
        if(intPBackslash!=-1){
          strFileName = strFileName.substring(intPBackslash+1);
        }
        //stream = item.getInputStream();
        aryBytesFile = item.get();
      }
      }
  }

  Class.forName("com.mysql.jdbc.Driver");
  String strConn = "jdbc:mysql://localhost/test"
    +"?user=Mulder&password=TrustNo1"
    +"&useUnicode=true&characterEncoding=utf-8";
  Connection conn = DriverManager.getConnection(strConn);
  PreparedStatement pstmt = conn.prepareStatement("INSERT INTO blob_test set title=?,file_name=?,data=?");
  pstmt.setString(1,strTitle);
  pstmt.setString(2,strFileName);
  //pstmt.setBlob(3,stream);
  pstmt.setBytes(3,aryBytesFile);
  pstmt.executeUpdate();
  conn.close();
}catch(SizeLimitExceededException e){ //←★
  strErrorMessage="フィルサイズが制限(1Mバイト)を超えています";
}
%>
<HTML><HEAD><TITLE>BLOB UPLOAD TEST</TITLE></HEAD>
<BODY>
<P>
<%if(strErrorMessage==null){//←★%>
  アップロード完了<BR>
  <a HREF="BlobList.jsp">一覧</a>
<%}else{%>
  <%=strErrorMessage%><BR>
  <INPUT TYPE="button" VALUE="戻る" ONCLICK="history.back()">
<%}%>
</P>
</BODY>
</HTML>
上のコードでは
upload.setSizeMax(1000000);
で上限を1Mバイトに設定しています。アップロードされたファイルがこのサイズを超えるとSizeLimitExceededException がスローされるので、これをキャッチしてstrErrorMessageにメッセージを書き込みます。

画面を表示する際にstrErrorMessageがnullかどうかをチェックして表示を切り替えています。
投稿:竹形 誠司[takegata]/2009年 02月 21日 12時 30分 /更新:2009年 02月 21日 12時 31分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様
ご指導ありがとうございます。
My.iniにmax_allowed_packet=10Mを追加しましたら、デスクトップにある2M程度のjpgファイルをアップロードすることが可能になりました。しかし、Cドライブの中の600k程度のファイルをアップロードしたら、以下のような別なエラーが発生しました。どうしてでしょうか。
それと、一覧画面でファイル名をクリックしてダウンロードした場合、どのファイルも拡張子なしでFileDownloadという名前になってしまいます。
どうかご教示おねがいいたします。
org.apache.jasper.JasperException: An exception occurred processing JSP page /BlobUploadProcess.jsp at line 42

39:            !strFileName.endsWith(".txt") &&
40:            !strFileName.endsWith(".doc") &&
41:            !strFileName.endsWith(".png") ){
42:                throw new IllegalArgumentException("送信可能なファイルの拡張子は、jpg、gif、xls、doc、txt、pdfのいずれかです");
43:            }
44:            int intPBackslash = strFileName.lastIndexOf("\\");
45:            if(intPBackslash!=-1){


Stacktrace:
    org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:505)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:416)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


原因

java.lang.IllegalArgumentException: 送信可能なファイルの拡張子は、jpg、gif、xls、doc、txt、pdfのいずれかです
    org.apache.jsp.BlobUploadProcess_jsp._jspService(BlobUploadProcess_jsp.java:100)
    org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:374)
    org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:342)
    org.apache.jasper.servlet.JspServlet.service(JspServlet.java:267)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


投稿:スワジランド[swajiland]/2009年 02月 21日 12時 44分 /更新:2009年 02月 21日 12時 45分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
コード全体を見てみないと何とも言えませんが、ファイル名をMySQLに入れる時点でFileDownloadという名前になってしまっているのではないでしょうか。
投稿:竹形 誠司[takegata]/2009年 02月 21日 13時 00分 /更新:2009年 02月 21日 13時 00分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様
MySQLへ登録しているfile_nameはアップロードしたファイル名になっています。
それと、txtファイルをアップロードして、それをダウンロードしたときに
javax.servlet.ServletException: サーブレットの実行により例外を投げました


原因

java.lang.NoClassDefFoundError: javax/servlet/ServletInputStream
    org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:126)
    UploadFile.doPost(UploadFile.java:53)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


原因

java.lang.ClassNotFoundException: javax.servlet.ServletInputStream
    java.net.URLClassLoader$1.run(URLClassLoader.java:200)
    java.security.AccessController.doPrivileged(Native Method)
    java.net.URLClassLoader.findClass(URLClassLoader.java:188)
    java.lang.ClassLoader.loadClass(ClassLoader.java:307)
    sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    java.lang.ClassLoader.loadClass(ClassLoader.java:252)
    java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
    org.apache.commons.fileupload.servlet.ServletFileUpload.parseRequest(ServletFileUpload.java:126)
    UploadFile.doPost(UploadFile.java:53)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:717)


注意 原因のすべてのスタックトレースは、Apache Tomcat/6.0.18のログに記録されています


というエラーが発生しました。プログラムはご指導いただいたものをほとんど無修正で使用しています。テーブルも同一のものを使用しています。
ご指導お願い申し上げます。
投稿:スワジランド[swajiland]/2009年 02月 21日 14時 16分 /更新:2009年 02月 21日 14時 16分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
うーむ、そうですか。
ちょっと調べるのでお待ちください。
投稿:竹形 誠司[takegata]/2009年 02月 21日 14時 47分 /更新:2009年 02月 21日 15時 30分
ダウンロードの際のファイル名
by 竹形 誠司[takegata]
ダウンロードの際のファイル名がFileDownloadになってしまう件は、私の勘違いでした。
response.setContentType("application/octet-stream");
の下あたりに
response.setHeader("Content-Disposition"," attachment; filename=\"" + strFileName +"\"");
を入れてみてください。

アップロードの際にエラーが出る件は引き続き調べます。
投稿:竹形 誠司[takegata]/2009年 02月 21日 15時 28分 /更新:2009年 02月 21日 15時 28分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
アップロードの際のエラーはよく分かりませんね。エラーメッセージが
java.lang.IllegalArgumentException: 送信可能なファイルの拡張子は、jpg、gif、xls、doc、txt、pdfのいずれかです
とのことですが、アップロードしようとしているファイルの拡張子を判別している所が問題なのではないでしょうか。
投稿:竹形 誠司[takegata]/2009年 02月 21日 16時 43分 /更新:2009年 02月 21日 16時 43分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様
どうもありがとうございました。
無事にアップロード、ダウンロードともにできるようになりました。
アップロードの失敗の原因はよくわかりませんが
          if(!strFileName.endsWith(".jpg") &&
            !strFileName.endsWith(".gif") &&
            !strFileName.endsWith(".pdf") &&
            !strFileName.endsWith(".xls") &&
            !strFileName.endsWith(".txt") &&
            !strFileName.endsWith(".doc") &&
            !strFileName.endsWith(".png") ){
//              throw new IllegalArgumentException("送信可能なファイルの拡張子は、jpg、gif、xls、doc、txt、pdfのいずれかです");
            }
のようにコメントアウトにしたところ、問題なく通るようになりました。
必ずしも大きいファイルの時にここでひっかかっているわけではないみたいです。600kb程度でひっかかっていましたが、3Mbでは問題なく通っていましたのでとりあえずこの1行だけコメントにしたところ難なく通ってしまいました。
本当にありがとうございました。1日で解決できて感謝しております。
ラッキー!!
投稿:スワジランド[swajiland]/2009年 02月 22日 00時 15分 /更新:2009年 02月 22日 00時 15分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
画像に限定しないのであれば、拡張子のチェックは不要でしたね。↑のソースも修正しました。

ただ、ダウンロードのコードには少し問題があって、ファイル名に日本語が含まれる場合は、保存するファイルの名前が化けてしまいます。これはブラウザに依存する要素もあって、なかなか一筋縄ではいかないようです。
投稿:竹形 誠司[takegata]/2009年 02月 22日 00時 30分 /更新:2009年 02月 22日 00時 30分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様
昨日はご指導誠にありがとうございました。
おかげさまで昨夜はぐっすり眠ることができました。(朝の4時頃までいろいろやってましたけど・・・)
お昼過ぎに起きて、日本語名のファイルをアップロード、ダウンロードしたら文字化けしたのでギョギョ!!いったい何が起こったのか?昨日と状態が変わってしまったのか?と思いきや昨夜は全て英数文字のファイル名しかアップロード、ダウンロードしていませんでした。
そして再度質問しようとしたら、既に先生からは先を読まれた書き込みがされていたので、恐れ入りました。結果的には残念。
この問題はcharset=Shift_JISのような呪文で解決できないのでしょうか。
折角MySQLには正しいファイル名が保存されているのにダウンロード時に文字化けしてしまうのはもったいない限りです。
投稿:スワジランド[swajiland]/2009年 02月 22日 14時 40分 /更新:2009年 02月 22日 14時 40分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
ダウンロード時に日本語のファイル名が化けてしまう問題については、「Content-Disposition 日本語」でGoogleを検索するといろいろ情報が出てきます。

http://www.google.co.jp/search?q=Content-Disposition+%E6%97%A5%E6%9C%AC%E8%AA%9E&lr=lang_ja&ie=utf-8&oe=utf-8

私もなんとか解決方法を見つけたいと思っています。何か分かれば、またここに書きますね。


投稿:竹形 誠司[takegata]/2009年 02月 22日 14時 55分 /更新:2009年 02月 22日 14時 55分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
↓のサイトを参考にコードを修正してみました。IEとFirefoxはこれで行けそうな感じです。
http://blog.flup.jp/2008/04/04/servlet_japanese_filename_download/
package jp.veltec.test;

import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.*;
import java.sql.*;
import org.apache.commons.codec.binary.Base64;
import java.net.URLEncoder;
import javax.mail.internet.MimeUtility;

public class FileDownload extends HttpServlet {

  public void doGet(HttpServletRequest request, HttpServletResponse response)
  throws ServletException, IOException{
    String strId = request.getParameter("id");
    String strConn = "jdbc:mysql://localhost/test"
      +"?user=Mulder&password=TrustNo1"
      +"&useUnicode=true&characterEncoding=utf-8";
    Blob data = null;
    InputStream is = null;
    String strFileName = null;
    try{
      Class.forName("com.mysql.jdbc.Driver");
      Connection conn = DriverManager.getConnection(strConn);
      Statement stmt = conn.createStatement();
      ResultSet rs = stmt.executeQuery("SELECT file_name,data FROM blob_test WHERE id = " + strId);
      if(rs.next()){
        data = rs.getBlob("data");
        strFileName = rs.getString("file_name");
      }else{
        throw new IllegalArgumentException("ファイルがありません");
      }
      is = data.getBinaryStream();
      conn.close();
    }catch(SQLException e){
      throw new ServletException(e.getMessage());
    }catch(ClassNotFoundException e){
      throw new ServletException(e.getMessage());
    }
    String strFileNameReturn = null;
    //↓★
    if(request.getHeader("User-Agent").indexOf("MSIE") != -1){
      strFileNameReturn = URLEncoder.encode(strFileName,"utf-8");
    }else{
      strFileNameReturn = MimeUtility.encodeWord(strFileName, "ISO-2022-JP", "B");
    }
    ServletOutputStream output = response.getOutputStream();
    if(strFileName.endsWith(".jpg")){
      response.setContentType("image/jpg");
    }else if(strFileName.endsWith(".gif")){
      response.setContentType("image/gif");
    }else if(strFileName.endsWith(".png")){
      response.setContentType("image/png");
        }else if(strFileName.endsWith(".pdf")){
            response.setContentType("application/pdf");
    }else{
      response.setContentType("application/octet-stream");
      response.setHeader("Content-Disposition"," attachment; filename=\"" + strFileNameReturn +"\""); //←★
    }
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[10000];
    while(true){
      int length = is.read(buffer);
      if(length==-1){
        break;
      }
      baos.write(buffer,0,length);
    }
    baos.writeTo(output);
    output.flush();
  }
}
MimeUtilityはJavaMailのmail.jarに入っています。
投稿:竹形 誠司[takegata]/2009年 02月 22日 16時 05分 /更新:2009年 02月 22日 16時 05分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様

いろいろとご指導ありがとうございます。
日本語ファイル名がアップロード、ダウンロードできました。
ただ、ファイル名が全角文字で19文字以上になるとなぜが、文字化けします。もし何か対策がありましたら教えてください。よろしくおねがいします。
投稿:スワジランド[swajiland]/2009年 02月 22日 21時 24分 /更新:2009年 02月 22日 21時 24分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
IEでダウンロードする際に長い日本語ファイル名が化けてしまうのは、仕方ないみたいですねぇ。URLエンコードせずにShift_JISでファイル名を送ることもできるようですが、その場合は特定の文字が化けるという現象が起こるようです。IEがRFCに合わせてくれればいいんですけどねぇ。

また、IE6とIE7でも挙動が異なるらしく、今の所は対処方法に決定版が無いような感じです。次善の策として、アップロード時にファイル名の文字数を制限するようなコードを入れてみるのはどうでしょうか。

参考:
http://www.agile-tech.com/blogs/dev/2007/12/contentdisposition.html
http://support.microsoft.com/kb/436616/ja
http://moodle.org/mod/forum/discuss.php?d=72567
投稿:竹形 誠司[takegata]/2009年 02月 22日 23時 51分 /更新:2009年 02月 22日 23時 55分
RE:MySQLにバイナリデータを保存する
by スワジランド[swajiland]
竹形様

本当にいろいろとご指導くださいましてありがとうございました。
一冊の本に出会って、さらにその著者と対話できるというまさにIT技術のすごさをこの掲示板に見ることができました。
もうあまり若くはない私も、昔にプログラムを少しかじっていたころの興奮を覚えました。
自分の試してみたかったことが、2ヶ月でほぼ形になって見ることができました。そのためにかかった授業料は竹形様の御本以外にJAVA,MySQL,JSPサーブレット,Eclipseの4冊の本だけでした。オープンソース恐るべしです。
あとは、インターネットで無償提供されている多くの企業や優秀なプログラマの情報のおかげでした。これからもますます最先端でのご活躍期待しております。
私ももう少し何かチャレンジしてみたいと思います。
分からないことが出てきたら、またこちらを訪れさせていただきます。
どうもありがとうございました。
投稿:スワジランド[swajiland]/2009年 02月 24日 02時 29分 /更新:2009年 02月 24日 02時 30分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
どういたしまして、当面の目標にだいぶ近付かれたようで何よりです。

以前はプログラマー35歳定年説などという話もあり、確かにCやC++などは年を取ってくるとなかなか厳しいものがありますが、デバッグの楽なJavaの登場でプログラマーの定年は大幅に延びたように思います。私も40代半ばですが、まだまだ行けそうな感じです。

今回いただいたご質問では、私もいろいろ勉強になりました。また何かありましたらよろしくお願いします。
投稿:竹形 誠司[takegata]/2009年 02月 24日 04時 36分 /更新:2009年 02月 24日 04時 39分
RE:MySQLにバイナリデータを保存する
by ara[ikegi]
DAOの方式で同じ事をしようとしています。

pstmt.setBlob(3,stream);
となっている箇所のDAOでの扱いが分かりません。
StreamとBlobの変換が、上記の方式なら一気に
変換できるようですが、DAOでは適切な方法が
みつかりません。

もし分かるようでしたら教えて下さい。

投稿:ara[ikegi]/2011年 08月 11日 00時 21分 /更新:2011年 08月 11日 00時 22分
RE:MySQLにバイナリデータを保存する
by 竹形 誠司[takegata]
竹形です、どうも。
ちょっと漠然とした話で論点がよく分からないのですが、
具体的なコードなどを示していただけますか?
投稿:竹形 誠司[takegata]/2011年 08月 11日 09時 27分 /更新:2011年 08月 11日 09時 27分
RE:MySQLにバイナリデータを保存する
by ara[ikegi]
しばらく連絡できない状況でした。質問しておきながら済みません。

下記のZuに当たるところが、Mysqlでは、Blobです。
Streamで入ってくるファイルの実態をとのやり取りが、
うまくいきません。Stream から Blobに
pstmt.setBlob(3,stream);
を使われていますが、DOAではどうすれば良いのかと
いうことです。下記のサンプルのようにやると、
型が違うよとエラーになってしまいます。

public class Sample {
    private String uname;
    private Blob zu;
    public Sample(String uname , Blob zu ){
        this.uname = uname;
        this.zu = zu;
        }

    public Know_how_work(){
    }
    public String getUname() {return uname;}
    public void setUname(String uname) {this.uname = uname;}
    public Blob getZu() {return zu;}
    public void setZu(Blob zu) {this.zu = zu;}
}


SampleDAO
public void create(Sample sch) {
    Connection con = null;
    con = createConnection();
    String sql = "insert into sample values(?, ?)";
    PreparedStatement stmt = con.prepareStatement(sql);
    stmt.setString(1, sch.getUname());
    stmt.setBlob(2, sch.getZu());
    stmt.executeUpdate();
    closeConnection(con);
    }


処理ロジック
Sample sch = new Sample();
    sch.setuname(uname_f);
    sch.setZu(stream_f);
    SampleDAO dao = new SampleDAO();
    dao.create(sch);

投稿:ara[ikegi]/2011年 08月 22日 13時 35分