JSPから送信された画像を表示した後、MySQLのBlob型のカラムに格納する

# はじめに
私は今、学校の課題でECサイトをチームで開発しています。その際に、画面から指定された画像をMySQLデータベースのBlob型のカラムに保存するのに手間取ったので、そのやり方を記事として投稿します。

# やりたいこと
画面で画像を選択する
↓
画面遷移して選択した画像を表示する
↓
画像をデータベースに格納する

# 画面から画像を選択する
```html
<form action="displayImage" method="post" enctype=multipart/form-data>
  <p>画像</p>
  <input type="file" name="image" accept="image/*">
  <input type="submit" value="送信">
</form>
```
上記のようにinputタグの`type`を`file`に指定することでバイナリファイルを選択することができるようになります。
その際にformタグで`enctype=multipart/form-data`を指定します。これを指定することで複数の種類のデータをサーバーに送信することができるようになります。
また、inputタグで`accept="image/*"`を選択することで選択できるファイルを画像のみに制限することができます。

# 選択された画像を取得する
```java
@WebServlet("/displayImage")
@MultipartConfig(location = "/tmp")
public class DisplayImageServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Part part = request.getPart("image");
        InputStream inputStream = part.getInputStream();

        byte[] byte = convertInputStreamToByteArray(inputStream);
        ImageBean imageBean = new ImageBean;
        imageBean.setImage(byte);

        HttpSession session = request.getSession();
        session.setAttribute("imageBean", imageBean);

        request.getRequestDispatcher("WEB-INF/jsp/image_view.jsp").forward(request, response);
    }

    //InputStreamをByte配列にする
    public byte[] convertInputStreamToByteArray(InputStream inputStream) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int nRead;
        byte[] data = new byte[16777215];
        while ((nRead = inputStream.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        return buffer.toByteArray();
    }
}
```
画面から送信された画像を取得するServletでは、`@MultipartConfig`アノテーションをつける必要があります。
multipart/form-dataで送信されたデータは、`getParameter()`ではなく`getPart()`で取得します。
その後、取得したデータを`InputStream`に変換し、さらにバイト配列に変換します。
そして、変換したバイト配列をBeanに格納し、セッションに保存します。

# 確認画面で画像を表示する
```html
<p>画像</p>
<img src="getImage" alt="画像">
<form action="uploadImage" method="post">
  <input type="submit" value="格納">
</form>
```
```java
@WebServlet("/getImage")
public class GetImageServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        InputStream inputStream = new ByteArrayInputStream(((ImageBean) session.getAttribute("imageBean")).getImage());

        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        BufferedImage img = ImageIO.read(bufferedInputStream);

        response.setContentType("image/png");
        OutputStream outputStream = response.getOutputStream();
        ImageIO.write(img, "png", outputStream);
        outputStream.flush();
    }
}
```
imgタグの`src`に画像取得用の`GetImageServlet`のアノテーションを指定することでServletを介して画像を取得します。
`GetImageServlet`では、先ほどセッションに保存した`ImageBean`から画像のバイト配列を取得し、InputStreamに戻した後に、それを読み込んで画面に返しています。

# 画像をデータベースに格納する
```sql
CREATE TABLE `image` (
  `image` mediumblob
) 
```
```java
@WebServlet("/uploadImage")
public class UploadImageServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        ImageBean imageBean = (ImageBean) session.getAttribute("imageBean");

        ImageDao imageDao = new ImageDao();
        imageDao.uploadImage(imageBean.getImage());
    }
}
```
```java
public class ImageDao {
    public void uploadImage(byte[] byte){
        PreparedStatement stmt = null;

        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            con = DriverManager.getConnection("jdbc:mysql://localhost:3306/image", "root", "root");
            stmt = con.prepareStatement("INSERT INTO image (image) VALUES (?)");
            stmt.setBinaryStream(1, new ByteArrayInputStream(byte));
            stmt.executeUpdate();

        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                if (con != null) {
                    con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
```
画像をデータベースに格納します。バイト配列を`InputStream`に変換したものをSQLのプレースホルダーに`setBinaryStream()`を使って格納します。最後にSQLを発行すればデータベースへの画像の格納は完了です。

ここで、もし`DisplayImageServlet`で画像データを`InputStream`に変換した後、バイト配列に変換していないと、画像をうまくデータベースに格納することができません。
バイト配列に変換しない場合、画像を表示する際に`GetImageServlet`で`InputStream`を`read`するとデータの最後まで読み込むので、`ImageDao`で`setBinaryStream()`を使うときに`InputStream`の終端からの値を格納するのが原因っぽい？

# 参考
[[フロントエンド] multipart/form-dataを理解してみよう](https://www.yoheim.net/blog.php?q=20171201)
[JSP/サーブレット ファイルアップロードの実装](https://qiita.com/ohke/items/bec00a69d3f538aab06b)
[サーブレット・JSPでDBに登録されている画像を表示](https://e-tamae.hatenadiary.org/entries/2015/06/05)