【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 データ型にマッピングされます。

#####【参考資料】
https://help.sap.com/viewer/cbed2190ee2d4486b0bbe0e75bf4b636/16.0.3.0/ja-JP/02b0331bcbaa414fb687dfccdbd64cbb.html

##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);
	}
}
```

###画面イメージ
#####参照ボタン押下
![スクリーンショット 2021-05-28 12.06.14.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411771/b04002d3-dfe5-bbf6-07e8-7a74e42d1736.jpeg)
#####アップロードする画像を選択
![スクリーンショット 2021-05-28 12.08.06.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411771/3dd0de36-0dea-116a-bc38-7ad45a6e5e67.jpeg)

#####プレビュー表示
![スクリーンショット 2021-05-28 12.06.57.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411771/dba13136-9c70-2dd4-57e1-f76a3608dcd8.jpeg)

###送信ボタン押下時処理
`contentType`にファイルのデータタイプを設定し，`processData`は`false`にしています。
`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();
```

###テーブル確認
テーブルにデータが保存されていることを確認します。
![スクリーンショット 2021-05-28 13.33.42.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411771/8a098360-7dd8-3193-2e51-094d7ea284f5.jpeg)


##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確認
#####画像取得ボタン押下
![スクリーンショット 2021-05-28 14.29.50.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411771/5e288a4f-2017-dc35-5792-878d73f67b87.jpeg)

#####プレビュー表示
![スクリーンショット 2021-05-28 14.30.02.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411771/8c38603c-b7d7-9e82-bcc8-47a2fd47debc.jpeg)



##おわりに
今回は画像ファイル登録のみの紹介になりますが，同様の方法で.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