話題の記事

【HTML5】Indexed Database API を真面目に勉強してみる

2013.04.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

最近、HTML5アプリ案件でローカルにデータを保存する必要があり、Local Storageを使うかIndexed DBを使うか検討しました。
その際、Indexed DBに関していろいろと調査したので残しておきます。

Indexed Database APIとは

キー・バリュー型のデータベースです。まだワーキングドラフト(草案)の状態のため今後、仕様が変わる可能性があります。
ネットで探すとIndexed DBの記事は見つかるのですが、記事が古いとエラーが発生しました。この記事のソースコードも今日時点(2013/4/25)では動きますが今後動かなくなる可能性があります。

Local Storageと何が違うのか

Local Storageと違うところは以下の3点です。

  1. Indexed DBはキー以外の項目にインデックスを張ることでキー以外の項目も検索条件にできます。
  2. Indexed DBはトランザクションが使えます。
  3. Indexed DBはオブジェクトストアでデータを区別できます。オブジェクトストアとはRDBでいうテーブルのようなものです。

実装してみる

動作確認はGoogle Chrome26.0 とFirefox20.0 、IE10でしています。
Safari 6 は残念ながらまだIndexed DBをサポートしていません。
※最初、IE10もサポートしていないと書きましたが、window.msIndexedDBのようにベンダープレフィックスを付ければ利用可能でした。申し訳ありません。

データベースの作成

Firefoxで使うためには window.mozIndexedDB、IE10で使うためには window.msIndexedDB のようにベンダープレフィックスを付けてIDBFactoryオブジェクトを取得します。
データベース名とオブジェクトストア名などは私は以下の値にしていますが、任意なので置き換えて下さい。

  • データベース名:mydb
  • オブジェクトストア名:mystore
  • インデックス名:myvalueIndex
  • キー項目名:mykey
  • バリュー項目名:myvalue
    • var db;
      var indexedDB = window.indexedDB || window.mozIndexedDB || window.msIndexedDB;
      if (indexedDB) {
      	// データベースを削除したい場合はコメントを外します。
      	//indexedDB.deleteDatabase("mydb");
      	var openRequest = indexedDB.open("mydb", 1.0);
      	
      	openRequest.onupgradeneeded = function(event) {
      		// データベースのバージョンに変更があった場合(初めての場合もここを通ります。)
      		db = event.target.result;
      		var store = db.createObjectStore("mystore", { keyPath: "mykey"});
      		
      		// インデックスを作成します。
      		store.createIndex("myvalueIndex", "myvalue");
      	}
      	
      	openRequest.onsuccess = function(event) {
              db = event.target.result;
          }
      } else {
      	window.alert("このブラウザではIndexed DataBase API は使えません。");
      }

      登録・更新

      まずは登録・更新処理です。すでにキーが登録されている場合は更新、ない場合は登録になります。
      登録・更新にはIDBObjectStoreクラスのputメソッドを使います。
      書き込みに失敗した場合は、自動的にロールバックされるようですが、明示的にロールバックしたい場合は、IDBTransactionクラスのabortメソッドでロールバックします。

      参照、登録、更新、削除での基本的な流れとしては

      1. データベース(IDBDatabase)からトランザクション(IDBTransaction)を取得。
      2. トランザクションからオブジェクトストア(IDBObjectStore)を取得。
      3. オブジェクトストアのメソッドを呼び出す。

      という流れになります。

      var key = [キー];
      var value = [バリュー];
      var transaction = db.transaction(["mystore"], "readwrite");
      var store = transaction.objectStore("mystore");
      var request = store.put({ mykey: key, myvalue: value});
      request.onsuccess = function (event) {
      	// 更新後の処理
      }

      キーを指定して参照

      キーを指定して参照するにはIDBObjectStoreクラスのgetメソッドを使います。

      var key = [バリューを取得したいキー];
      var transaction = db.transaction(["mystore"], "readwrite");
      var store = transaction.objectStore("mystore");
      var request = store.get(key);
      request.onsuccess = function (event) {
      	if (event.target.result === undefined) {
      		// キーが存在しない場合の処理
      	} else {
      		// 取得成功
      		console.log(event.target.result.myvalue);
      	}
      }

      全件参照

      全件参照するにはIDBObjectStoreクラスのopenCursorメソッドを使います。
      イベントハンドラの中でIDBCursorWithValueを取得できるので、そこからデータを参照します。

      var transaction = db.transaction(["mystore"], "readwrite");
      var store = transaction.objectStore("mystore");
      var request = store.openCursor();
      
      request.onsuccess = function (event) {
      
      	if(event.target.result == null) {
      		return;
      	}
      	
      	var cursor = event.target.result;
      	var data = cursor.value;
      	console.log("key:"  + cursor.key +  "  value:" + data.myvalue);
      	cursor.continue();
      }

      キーを指定して削除

      キーを指定して削除するにはIDBObjectStoreクラスのdeleteメソッドを使います。

      var key = [削除したいキー];
      var transaction = db.transaction(["mystore"], "readwrite");
      var store = transaction.objectStore("mystore");
      var request = store.delete(key);
      request.onsuccess = function (event) {
      	// 削除後の処理
      }

      全件削除

      キーを指定して削除するにはIDBObjectStoreクラスのclearメソッドを使います。

      var transaction = db.transaction(["mystore"], "readwrite");
      var store = transaction.objectStore("mystore");
      var request = store.clear();
      request.onsuccess = function (event) {
      	// 全件削除後の処理
      }

      インデックスと条件の範囲指定

      Local Storageではできない、キー値以外での検索と条件の範囲指定を試してみたいと思います。
      今回はIDBKeyRangeのboundメソッドを試しましたが、他にもいろいろあります。以下のサイトが参考になりました。
      IDBKeyRange - IndexedDB | MDN

      var min = [下限値(数値)];
      var max = [上限値(数値)];
      
      var transaction = db.transaction(["mystore"], "readwrite");
      var store = transaction.objectStore("mystore");
      var index = store.index("myvalueIndex");
      
      var range = IDBKeyRange.bound(min, max);
      var request = index.openCursor(range);
      
      request.onsuccess = function (event) {
      	if(event.target.result == null) {
      		return;
      	}
      	
      	var cursor = event.target.result;
      	var data = cursor.value;
      	console.log("value:" + data.myvalue + "  key:"  + data.mykey);
      	cursor.continue();
      }

      こちらにサンプルを置いておきました。 以下がサンプルのソースです。

      <!DOCTYPE html>
      <html>
      <head>
      	<meta charset="utf-8"/>
      	<title>Indexed DataBase APIサンプル</title>
      </head>
      <body>
      	<h3>登録・更新</h3>
      	キー:<input type="text" id="newkey" />
      	バリュー:<input type="number" id="newvalue" />
      	<input type="button" value="設定" onclick="setValue()"/>
      	<hr />
      	
      	<h3>参照</h3>
      	取得したい値のキー<input type="text" id="selectkey" />
      	<input type="button" value="取得" onclick="getValue()"/>
      	<input type="button" value="全取得" onclick="getAll()"/>
      	<input type="button" value="件数表示" onclick="getCount()"/>
      	<hr />
      	
      	<h3>削除</h3>
      	削除したい値のキー<input type="text" id="deletekey" />
      	<input type="button" value="削除" onclick="deleteValue()"/>
      	<input type="button" value="全削除" onclick="deleteAll()"/>
      	<hr />
      	
      	<h3>インデックスを使った検索</h3>
      	<input type="number" id="selectValue1" />~<input type="number" id="selectValue2" />
      	<input type="button" value="検索" onclick="getKey()"/>
      	<hr />
      	
      	<h3>処理結果</h3>
      	<div id="result"/>
      </body>
      <script>
      	var db;
      	var indexedDB = window.indexedDB || window.mozIndexedDB || window.msIndexedDB;
      	if (indexedDB) {
      		// データベースを削除したい場合はコメントを外します。
      		//indexedDB.deleteDatabase("mydb");
      		var openRequest = indexedDB.open("mydb", 1.0);
      		
      		openRequest.onupgradeneeded = function(event) {
      			// データベースのバージョンに変更があった場合(初めての場合もここを通ります。)
      			db = event.target.result;
      			var store = db.createObjectStore("mystore", { keyPath: "mykey"});
      			store.createIndex("myvalueIndex", "myvalue");
      		}
      		
      		openRequest.onsuccess = function(event) {
      	        db = event.target.result;
              }
      	} else {
      		window.alert("このブラウザではIndexed DataBase API は使えません。");
      	}
      	
      	function setValue(event) {
      		var key = document.getElementById("newkey").value;
      		var value = Number(document.getElementById("newvalue").value);
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		
      		var request = store.put({ mykey: key, myvalue: value});
      		request.onsuccess = function (event) {
      			document.getElementById("newkey").value = "";
      			document.getElementById("newvalue").value = "";
      		}
      	}
      	
      	function getValue(event) {
      		var key = document.getElementById("selectkey").value;
      		var result = document.getElementById("result");
      		result.innerHTML = "";
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		
      		var request = store.get(key);
      		request.onsuccess = function (event) {
      		
      			if (event.target.result === undefined) {
      				result.innerHTML = "指定したキーは存在しません。";
      			} else {
      				result.innerHTML = event.target.result.myvalue + "<br/>";
      			}
      		}
      	}
      	
      	function getAll(event) {
      	
      		var result = document.getElementById("result");
      		result.innerHTML = "";
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		var request = store.openCursor();
      		
      		request.onsuccess = function (event) {
      		
      			if(event.target.result == null) {
      				return;
      			}
      			
      			var cursor = event.target.result;
      	        var data = cursor.value;
      			result.innerHTML += "key:"  + cursor.key +  "  value:" + data.myvalue + "<br/>";
      			
      			cursor.continue();
      		}
      	}
      	
      	function getCount(event) {
      	
      		var result = document.getElementById("result");
      		result.innerHTML = "";
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		var request = store.count();
      		
      		request.onsuccess = function (event) {
      			result.innerHTML = event.target.result + "件";
      		}
      	}
      	
      	function deleteValue(event) {
      		var key = document.getElementById("deletekey").value;
      		var result = document.getElementById("result");
      		result.innerHTML = "";
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		
      		var request = store.delete(key);
      		request.onsuccess = function (event) {
      			result.innerHTML = "削除しました。";
      		}
      	}
      	
      	function deleteAll(event) {
      	
      		var result = document.getElementById("result");
      		result.innerHTML = "";
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		var request = store.clear();
      		
      		request.onsuccess = function (event) {
      			result.innerHTML = "クリアしました。";
      		}
      	}
      	
      	function getKey(event) {
      	
      		var value1 = Number(document.getElementById("selectValue1").value);
      		var value2 = Number(document.getElementById("selectValue2").value);
      		
      		var result = document.getElementById("result");
      		result.innerHTML = "";
      		
      		var transaction = db.transaction(["mystore"], "readwrite");
      		var store = transaction.objectStore("mystore");
      		var index = store.index("myvalueIndex");
      		
      		var range = IDBKeyRange.bound(value1, value2);
      		var request = index.openCursor(range);
      		
      		request.onsuccess = function (event) {
      		
      			if(event.target.result == null) {
      				return;
      			}
      			
      			var cursor = event.target.result;
      	        var data = cursor.value;
      			result.innerHTML += "value:" + data.myvalue + "  key:"  + data.mykey + "<br/>";
      			
      			cursor.continue();
      		}
      	}
      
      </script>
      </html>

      まとめ

      まだ仕様が変わる可能性があるため業務で使うにはまだ早いと思いますが、Local Storageに比べると機能が充実していると感じました。
      インデックスがかなり便利なので早く勧告候補になって欲しいですね。