【SAPUI5】UIからxsjsを経由してHANA DBに画像登録をする


HANA DBへの画像登録

はじめに

こんにちは。
HANAで作成したテーブルに対して,xsjsのREST APIを使用して画像アップロードする方法を調査しましたので共有します。
なお,今回の記事では画像ファイルをNCLOB型で作成したカラムに保存することを想定しています。

テーブル作成

SQL

xsjsでの扱いやすさの観点からデータ型はNCLOBで作成していきます。

CREATE COLUMN TABLE "STORE"."IMG_TEST"(
    "IMG_NAME" NVARCHAR(100),
    "IMG_VALUE" NCLOB ,
    PRIMARY KEY (
        "IMG_NAME"
    )
)

その他ファイルを扱えるデータ型

NCLOBの他にファイルを扱えるデータ型は以下です。

BLOB

BLOB データ型は、大量のバイナリデータを格納するために使用されます。BLOB 値は VARBINARY に変換できます。SAP HANA の BLOB データ型は SAP ASE の IMAGE データ型にマッピングされます。

CLOB

CLOB データ型は、大量の 7 ビット ASCII 文字データを格納するために使用されます。CLOB 値は VARCHAR に変換できます。SAP HANA の CLOB および BCLOB データ型は、SAP ASE の TEXT データ型にマッピングされます。

【参考資料】

UI側(保存処理)

fileUploaderの用意

ブラウザにファイルがアップロードができればなんでもいいですが,今回はsap.ui.unifiedのFileUploaderを使っていきます。
ここでchangeイベントを設定します change="onChangeImage"

<un:FileUploader id="fileUploader" buttonOnly="true" change="onChangeImage" width="100px" fileType="jpg,png,jpeg" tooltip="Upload your file"/>

プレビューの表示

選択した画像のプレビューをUIに表示します。

View側

viewでidを設定

<Image class="imagePreview" width="600px" id="image"></Image>

Controller側

changeイベントでブラウザにアップロードされたファイルを取得し,viewのidに対してsrcを設定します。
また,ここでファイルデータをURLに整形しreader.readAsDataURL(file);,必要な情報と合わせてモデルにセットしておきます。

onChangeImage: function (oEvent) {
    var _this = this;
    var oView = _this.getView();

    if (oEvent.getSource().oFileUpload.files.length > 0) {
        var file = oEvent.getSource().oFileUpload.files[0];
        var reader = new FileReader();

        //dataURL形式でファイルを読み込む
        reader.readAsDataURL(file);

        //ファイルの読込が終了した時の処理
        reader.onload = function () {
            // 送信用にモデルにファイル(URL化)を保持
            var imgData = reader.result;
            var fileType = file.type;
            var params = {
                imgData: imgData,
                fileType: fileType
            };
            var paramsJson = new JSONModel(params);
            oView.setModel(paramsJson, "params");
        };

        // Viewに画像をセット
        var path = URL.createObjectURL(file);
        this.getView().byId("image").setSrc(path);
    }
}

画面イメージ

参照ボタン押下

アップロードする画像を選択

プレビュー表示

送信ボタン押下時処理

contentTypeにファイルのデータタイプを設定し,processDatafalseにしています。
dataで送信するimageDataをURLのパラメータに設定しないために上記設定を記載します。

pressUploadButton: function () {
    var _this = this;
    var oView = _this.getView();

    var oFileUploader = this.getView().byId("fileUploader");
    var oStorage = jQuery.sap.storage(jQuery.sap.storage.Type.session);
    var params = oView.getModel("params").oData;

    //ajax
    _this._setCSRFToken();
    $.ajax({
        type: "GET",
        url: "/xxx/xxx/app/api/imgUploadTest.xsjs?name=" + oFileUploader.getValue(),
        processData: false,
        contentType: params.fileType,
        method: "POST",
        async: false,
        data: params.imgData
    }).done(function (data) {
        console.log(data);
    });
},
_setCSRFToken: function () {
    var thisController = this;
    jQuery.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (settings && settings.hasOwnProperty("type") && settings.type !== "GET") {
                var token = thisController._getCSRFToken();
                xhr.setRequestHeader("X-CSRF-Token", token);
            }
        },
        complete: function (xhr, textStatus) {
            var loginPage = xhr.getResponseHeader("x-sap-login-page");
            if (loginPage) {
                location.href = loginPage + "?x-sap-origin-location=" + encodeURIComponent(window.location.pathname);
            }
        }
    });
},

_getCSRFToken: function () {
    var token = null;
    jQuery.ajax({
        url: "/xxx/xxx/app/api/csrf.xsjs",
        type: "GET",
        async: false,
        beforeSend: function (xhr) {
            xhr.setRequestHeader("X-CSRF-Token", "Fetch");
        },
        complete: function (xhr) {
            token = xhr.getResponseHeader("X-CSRF-Token");
        }
    });
    return token;
}

xsjs(保存)

パラメータ取得

UIからajaxで送信されたパラメータを取得します。

var name = $.request.parameters.get("name");
var imgData = $.request.body.asString();

クエリ作成

// ■ db接続
var conn = $.db.getConnection();
conn.setAutoCommit(false);

// クエリの組み立て
var query = "INSERT INTO STORE.IMG_TEST (IMG_NAME, IMG_VALUE) VALUES ( ?, ? ) ";
var cntmt = conn.prepareStatement(query);
cntmt.setString(1, name);
cntmt.setString(2, imgData);
cntmt.execute();

テーブル確認

テーブルにデータが保存されていることを確認します。

UI(データ呼び出し)

今度は,保存された画像データをUIから呼び出して画面に表示します。

プレビュー用意

View側

こちらもidを指定します。

<Image class="imagePreview" width="600px" id="imageReturn"></Image>

画像呼び出し

Controller側

今回,URLパラメータ(name)はテーブルに登録されているIMG_NAMEを固定値でパラメータに設定します。

pressGetButton: function () {
    var _this = this;
    _this._setCSRFToken();

    var dataArr = {
        "name": "任意の画像名"
    };
    $.ajax({
        type: "GET",
        url: "/xxx/xxx/app/api/imgGetTest.xsjs",
        dataType: "json",
        method: "POST",
        async: false,
        data: dataArr
    }).done(function (data) {
        _this.getView().byId("imageReturn").setSrc(data[0].IMG_VALUE);
    });
},
_setCSRFToken: function () {
    var thisController = this;
    jQuery.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (settings && settings.hasOwnProperty("type") && settings.type !== "GET") {
                var token = thisController._getCSRFToken();
                xhr.setRequestHeader("X-CSRF-Token", token);
            }
        },
        complete: function (xhr, textStatus) {
            var loginPage = xhr.getResponseHeader("x-sap-login-page");
            if (loginPage) {
                location.href = loginPage + "?x-sap-origin-location=" + encodeURIComponent(window.location.pathname);
            }
        }
    });
},

_getCSRFToken: function () {
    var token = null;
    jQuery.ajax({
        url: "/xxx/xxx/app/api/csrf.xsjs",
        type: "GET",
        async: false,
        beforeSend: function (xhr) {
            xhr.setRequestHeader("X-CSRF-Token", "Fetch");
        },
        complete: function (xhr) {
            token = xhr.getResponseHeader("X-CSRF-Token");
        }
    });
    return token;
}

レスポンスで取得したデータは指定したidに対してsetSrc()でパスとして設定します。

xsjs(描画)

パラメータ取得

UIからajaxで送信されたパラメータを取得します。

// ■ データ取得 
var name = $.request.parameters.get("name");

クエリ作成

// ■ db接続
var conn = $.db.getConnection();
conn.setAutoCommit(false);

// クエリの組み立て
var query = "SELECT IMG_NAME, IMG_VALUE FROM STORE.IMG_TEST WHERE IMG_NAME = ? ";
var cntmt = conn.prepareStatement(query);
cntmt.setString(1, name);
var rs = cntmt.executeQuery();
var retarr = [];
while (rs.next()) {
    var record = {
        "IMG_NAME": rs.getString(1),
        "IMG_VALUE": rs.getString(2)
    };
    retarr.push(record);
}

if (cntmt) {
    cntmt.close();
}

UIへのレスポンス

var output = JSON.stringify(retarr);
$.response.setBody(output);

UI確認

画像取得ボタン押下

プレビュー表示

おわりに

今回は画像ファイル登録のみの紹介になりますが,同様の方法で.csvや.xslxデータも扱えるかと思います。
ただ,データの上限や総データ量が増加した際の処理,挙動については確認できていないので,扱いには注意が必要です。

また,今回紹介させていただいた方法の他に,UI側からファイルの保存・使用方法として,
データ用にサーバを用意してファイル自体はそちらに保存し,DBにはファイルパスのみ登録する方法もあります。

データの扱いの面でメリットデメリットあるかと思いますので,特性を理解して適材適所で使い分けする必要がありそうですね。
また,ファイル保存について何か進展があればここに追記していきます。

参照資料

https://blogs.sap.com/2016/09/01/upload-and-retrieve-image-using-sap-hana-xs-sap-ui5/
https://blogs.sap.com/2017/03/14/how-to-post-an-image-from-sapui5-and-store-in-hana-db-as-blob-using-xsjs/
https://help.sap.com/viewer/cbed2190ee2d4486b0bbe0e75bf4b636/16.0.3.0/ja-JP/02b0331bcbaa414fb687dfccdbd64cbb.html
https://javascript.keicode.com/newjs/arraybuffer.php#1