41から始めました

文字通り41歳から始めたブログです。DB(MySQL)を使ってお仕事してるので、DB周りの話を中心に最近始めたこととかをTwitterのノリで書いています。なお、本サイトにおいて示されている見解は私個人の見解であり、所属団体や組織を代表するものではありません。

リモートからBLOB型に画像やPDFファイルを登録する方法

この記事は MySQL Advent Calendar 2021 の5日目の記事です。

久々のブログです。

今年は私事が忙しくてサボりまくってましたが、年明けから頑張ろうかと思いそのためのリハビリです。

今回のネタは備忘録的に自分のために残したかったものです。

BLOB型へのファイルの登録

ローカルなら簡単にできるんだけど

BLOB型に画像やPDFファイルを登録する方法ですが、LOAD_FILE()って関数があって、通常はこれでBLOB型にファイルパスを入れて突っ込みます。

しかし、残念ながらMySQL Serverのあるインスタンス、つまりローカルにそのファイルが無いといけないという制約(*1)があります。

MySQL :: MySQL 8.0 リファレンスマニュアル :: 12.8 文字列関数および演算子

というわけで、リモートからのBLOB型カラムへのバイナリファイルの格納方法がMySQLのコマンドツールでは出来ません。

(マウントすればローカル扱いになるんですが、MySQL Severのローカルとして見える場所にファイルなんか置きたくないよ!って言う声はあるでしょう)

じゃあ、どうすれば?

そんな場合はプリペアドステートメントを使用すれば可能です。

プリペアドステートメント

MySQL :: MySQL 8.0 リファレンスマニュアル :: 13.5 プリペアドステートメント

MySQL C API client library for C プログラム、MySQL Connector/J for Java プログラム、MySQL Connector/NET for .NET を使用するプログラムなどのクライアントプログラミングインタフェースを介して使用できます。

ってわけで、簡易的なコードを書いてみました。

実行例

package java_mysql;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.sql.SQLException;
import java.sql.Blob;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;

public class Test {
    public static String URL = "jdbc:mysql://(ホストorIPアドレス):(ポート番号)/(データベース名)";
    public static String USER = "(DBユーザー名)";
    public static String PASSWD = "(DBパスワード)";
    public static String TEST_PDF_PATH = "(ファイルパスとファイル名)";
    public static void main(String[] args) {

        Connection conn;
        Statement st;
        PreparedStatement ps1;
        String sql1 = "INSERT INTO t_blob (b) VALUES(?)";
        byte[] data1;
        
        Blob blob;

        try {
            data1 = Files.readAllBytes(Paths.get(TEST_PDF_PATH));
        } catch(IOException ioex) {
            ioex.printStackTrace();
            return;
        }
        
        try {
            conn = DriverManager.getConnection(URL, USER, PASSWD);
            st = conn.createStatement();
    
            st.execute("CREATE TABLE IF NOT EXISTS t_blob (a SERIAL, b LONGBLOB)");
            st.execute("TRUNCATE t_blob");
            blob = conn.createBlob();
            blob.setBytes(1, data1);
            ps1 = conn.prepareStatement(sql1);
            ps1.setBlob(1, blob);
            ps1.executeUpdate();
            ps1.close();
            st.close();
            conn.close();
        } catch (Exception e) {
            if (e instanceof SQLException) {
            System.out.println("SQLState: " + ((SQLException)e).getSQLState());
            System.out.println("SQLErrorCode: " + ((SQLException)e).getErrorCode());
            System.out.println("SQLErrorMsg: " + ((SQLException)e).getMessage());
            }
            e.printStackTrace();
        }
    }

}

おおまかには以下のような流れです。

  1. Files.readAllBytesで対象の画像ファイルを読み込み、
  2. データベースへ接続
  3. テーブルが存在しない場合は再作成し、存在する場合にはTRUNCATE TABLEをします
  4. プリペアドステートメントを開始し、BLOBファイルを読み込み、DBに挿入します
  5. プリペアドステートメントとDBへの接続を切ります。

ちなみに

プリペアドステートメントじゃなくても、各言語のライブラリなどで格納するものがあったりもするので、その場合はそっちを参照してください。(試してはいない)

npm - mysql でリモートの MySQL サーバーにバイナリファイルのデータを挿入する : まだプログラマーですが何か?

PHP MySQL BLOB: Insert, Update, And Select BLOB Data

さいごに

普段は簡単なコーディングもしないんで、たまにこうやってちょっとしたもの作って、それが動くのを見るだけでも楽しいです。(というか、気軽なコーディングが一番楽しい)

参考資料

Writing and Reading MySQL BLOB Using JDBC

MySQL :: MySQL Connector/NET Developer Guide :: 5.6.3 Reading a BLOB from the Database to a File on Disk

注釈

*1 LOAD_FILE()には、ファイルはサーバーで読取り可能というほかに、そのサイズが max_allowed_packet バイト未満である必要があるという制約もあります。

明日は

明日は@hmatsu47 さんです。お楽しみに!