Indexed Database API について
Indexed Database API(以下、indexedDB)について、これまで追いかけてきた情報をとりまとめたので公開します。
indexedDBは当初は仕様が固まっておらず、サンプルコードも当然のように動かなかったので(今も動きませんが…)、検証するにはかなりハードな状況でした。最近になってどうにか動くようになってきたので、@komasshu さんと色々やり取りしながら一通りの動作を確認しました。
現時点で利用できるブラウザは Chrome 9 以降 または Firefox 4 beta 8 以降となります。まだまだ仕様は動いていますので、検証の際は、なるべく最新の開発版を使うことをおすすめします。本エントリーでは、Chrome 9 beta 、Firefox 4 beta 8 にて検証します。また、資料は、2011年1月20日時点の W3C Editor's Draft を参照しています。
1. Indexed Database API とは
indexedDBは、ブラウザが持つ NoSQL ベースのデータベースです。オリジン(Origin*1)ごとに固有の領域を持ち、複数のデータベースを保持することができます。
2. 基本的な使い方
(1) データベースの作成(接続)
現時点では indexedDB(IDBFactory)にベンダープリフィックスを付けてアクセスします。Chrome であれば、webkitIndexedDB。Firefoxであれば moz_indexedDB(beta8)、または mozIndexedDB(beta9)です。
サンプルコード1
//Chrome、Firefoxのベンダープリフィックス対応 var indexedDB = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB || window.moz_indexedDB; //データベース作成(接続) var db = null; var req = indexedDB.open("library"); //成功時コールバック req.onsuccess = function(evt) { db = evt.result; }; //失敗時コールバック req.onerror = function(err) { alert(err.code + ":" + err.message); };
データベース操作の結果は、リクエスト(IDBRequest)のコールバックに登録することになります。
(2) オブジェクトストア・インデックスの作成
オブジェクトストアは、データを格納するための入れ物です。RDBMSのテーブルに相当します。オブジェクトストア、及びインデックスの作成と削除はデータベースのバージョン変更時にしか行うことができませんので、注意が必要です。
サンプルコード2
//DBのバージョン変更 var verReq = db.setVersion("1.0"); verReq.onsuccess = function(evt) { //オブジェクトストア作成(in-line key として isbn を指定) var store = db.createObjectStore("books", "isbn", false); //Chromeの場合、以下のように記載 //var store = db.createObjectStore("books", {"keyPath": "isbn"}, false); //インデックス作成 store.createIndex("name", false); };
オブジェクトストアには、各データを一意に識別するキーが必要です。キーは、createObjectStore の第2引数で指定します。キーには、2種類あり、キーを指定した場合の in-line key 、省略した場合の out-of-line key があります。キーの種類、及び第3引数の autoIncrement の値でデータ追加時の動作が異なります。
・in-line key
データ内のプロパティをキーとします。データ追加時には、該当プロパティに値が存在する必要があります。
autoIncrement が true の場合、該当プロパティは存在しなくても問題ありません。
・out-of-line key
データの外にキーを持ちます。データ追加時には、別途キーを渡す必要があります。
autoIncrement が true の場合、別途キーを渡す必要はありません。
(3) データの追加・削除
オブジェクトストアに格納できるデータは、その名の通りオブジェクト(structured clone*2)です。データはトランザクションを経由して操作します。
サンプルコード3
//Chromeのベンダープリフィックス対応 var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction; //データ作成 var book = { name: "徹底解説 HTML5 APIガイドブック コミュニケーション系API編", description: "AjaxやHTTPで高速なリアルタイム通信アプリケーション開発を可能にする新APIの仕様と使い方を詳細に解説。", author: "小松 健作", isbn: "4798028215" }; //トランザクションの開始とオブジェクトストアの取得 var store = db.transaction([], IDBTransaction.READ_WRITE).objectStore("books"); //データ追加 store.add(book); //putでも同様の結果が得られる //データ削除 store.delete("4798028215");
オブジェクトストア作成時に、isbn を in-line key に指定しているので、データ作成時に値をセットした isbn が key になります。ただし Chrome では、まだ in-line key の指定が無視されるので、コメントのように記述しないと動作しません。createObjectStoreに第2引数に{"keyPath": "isbn"}のように指定すると動作するようです。
有効なキーは、string、date、long、float です。null 及び floatの Inifinite は有効なキーですが、NaN は無効です。また、undefined は許可されていません。
(4) データの検索・変更
データの検索方法には、1件だけ取得する get と 複数取得するための openCursor があり、データを変更したい場合、openCursor で取得してから行います。
サンプルコード4 〜 データの検索
//トランザクションの開始とオブジェクトストアの取得 var store = db.transaction().objectStore("books"); //Firefox4の場合、transactionの引数の省略ができないのでnullを入れておく //var store = db.transaction(null).objectStore("books"); //get var req = store.get("4798028215"); req.onsuccess = function(evt) { var value = evt.result; console.log(value); }; //openCursor、インデックス経由で取得 var req = store.index("name").openCursor("徹底解説 HTML5 APIガイドブック コミュニケーション系API編"); req.onsuccess = function(evt) { var cursor = evt.result; if ( cursor ) { console.log(cursor.value); //Firefox4の場合、index経由だとvalueにkeyが入っているので再度検索 //var getReq = store.get(key).onsuccess = function(getEvt){ // console.log(getEvt.result); //}; cursor.continue(); } };
get の結果に複数のデータがある場合は、最初の1件のみ取得できます。また、インデックスを利用する場合も get 、openCorsor 等、同様のメソッドがあるので同じ方法で取得することができます。
サンプルコード5 〜 データの変更
//Chromeのベンダープリフィックス対応 var IDBKeyRange= window.IDBKeyRange|| window.webkitIDBKeyRange; //トランザクションの開始とオブジェクトストアの取得(READ_WRITE) var store = db.transaction([], IDBTransaction.READ_WRITE).objectStore("books"); //キーを範囲指定するにはIDBKeyRangeを使う。 var req = store.openCursor(IDBKeyRange.lowerBound("4844329278", false)); req.onsuccess = function(evt) { var cursor = evt.result; if ( cursor ) { //description変更、changedフラグ追加 var book = cursor.value; book.description = "updated"; book.changed = true; //データ変更反映 cursor.update(book); cursor.continue(); } };
データの変更を行う場合は、トランザクションを READ_WRITE で開始します。カーソルを使ってデータを取得後、変更し、同じくカーソルの update メソッドを介して変更を反映します。プロパティの追加なども問題なくできます。
(5) その他 Tips
・データのソート
オブジェクトストア内のデータは、キーの昇順で格納されています。そのため、昇順で取得する必要があれば、そのまま取得します。降順で取得したければ、openCursor 時の第2引数で、IDBCursor.PREV を指定します。インデックスでも同様です。
//Chromeでの実行例 var req = db.transaction().objectStore("books").index("name").getCursor("", webkitIDBCursor.PREV); req.onsuccess = function(evt) { var cursor = evt.result; if ( cursor ) { //降順にデータ取得 console.log(cursor.value); cursor.continue(); } };
応用として、最少値、最大値を取得したい場合は、対象のプロパティにインデックスを作成して、最初の1件を取得すれば求められます。
キーの順序は、string、date、longとfloatとなっており、longとfloatはその数値で比較されます。
・トランザクションのコミットとロールバック
トランザクションのコミットは、別のトランザクションが開始されるか、トランザクションが発生したメソッドが終了すれば、暗黙的にコミットされます。ロールバックはエラーやタイムアウトが発生した場合には自動的にロールバックされますが、明示的に指定したい場合は、abort を使います。
//トランザクション開始 var tx = db.transaction([], IDBTransaction.READ_WRITE); /* 複数のデータ操作 */ //トランザクションの中断とロールバック tx.abort();
データベースのバージョン変更時は、リクエストの result に入っているトランザクションを使ってロールバックします。
//トランザクション開始 var req = db.setVersion("1.1"); req.onsuccess = function(evt) { var tx = evt.result; /* スキーマの変更など */ //トランザクションの中断とロールバック tx.abort(); };
3. 簡易API一覧
IDBFactory
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
open(name) | nameで指定されたDBを作成する。 既に存在していればDBに接続する。 |
○ | ○ | 以前あった第2引数のdescriptionは なくなりました |
deleteDatabase(name) | nameで指定されたDBを削除する。 | - | - | 未実装 |
IDBRequest
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
onsuccess | リクエスト成功時コールバック | ○ | ○ | |
onerror | リクエスト失敗時コールバック | ○ | ○ | |
readyState | リクエストの状態 LOADING: 実行中 DONE: 完了 |
○ | ○ |
IDBDatabase
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
name | DB名 | ○ | ○ | |
objectStoreNames | DBに格納されている全オブジェクトストアの名前 | ○ | ○ | |
version | DBのバージョン | ○ | ○ | |
close() | DB接続を閉じる。 | - | ○ | |
createObjectStore(name, keyPath, autoIncrement) | nameで指定されたオブジェクトストアを作成する。 | △ | ○ | Chrome は keyPath を{"keyPath": "keyPath"}のようにオブジェクトで渡します。 |
deleteObjectStore(name) | nameで指定されたオブジェクトストアを削除する。 | ○ | ○ | |
setVersion(version) | DBバージョンを更新する。 | ○ | ○ | 以下のメソッドは、バージョン更新のコールバックでしか実行できない。 createObjectStore deleteObjectStore createIndex deleteIndex |
transaction(storeNames, mode, timeout) | storeNamesで指定されたオブジェクトストア(デフォルト:全オブジェクトストア)を対象としてトランザクションを開始する。 modeでトランザクションの種類を指定する。 READ_ONLY: 読込専用 READ_WRITE: 書込可能 VERSION_CHANGE: バージョン更新 (デフォルト:READ_ONLY) timeout ミリ秒単位(デフォルト:ブラウザ固有) |
○ | ○ | 引数はすべてオプションだが、Firefox は transaction() のような記述ができない。Chrome、Firefoxともにtimeoutは無視される。 |
IDBObjectStore
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
name | オブジェクトストア名 | ○ | ○ | |
keyPath | キーのプロパティ名 | ○ | ○ | |
indexNames | オブジェクトストアに設定されたインデックス名 | ○ | ○ | |
put(value, key) | オブジェクトストアにデータを追加する。keyは、オブジェクトストア作成時に、キーとキーの自動生成を指定しない場合に必須となる。 | ○ | ○ | |
add(value, key) | putと同様。 | ○ | ○ | |
delete(key) | key(リテラルのキー値)のデータを削除する。 | ○ | ○ | |
get(key) | key(リテラルのキー値または、IDBKeyRange)のデータを取得する。複数ある場合は、最初の1件を返す。 | △ | ○ | ChromeはIDBKeyRangeでの指定ができない。 |
clear() | すべてのデータを削除する。keyは | - | ○ | |
openCursor(range, direction) | range(リテラルのキー値または、IDBKeyRange)で指定された範囲のデータを取得する。directionで取得順序を指定する。(IDBCursor) NEXT: 昇順 NEXT_NO_DUPLICATE: 昇順(重複なし) PREV: 降順 PREV_NO_DUPLICATE: 降順(重複なし) |
△ | △ | rangeについて、ChromeはIDBKeyRangeでの指定ができない。逆にFirefoxでは、リテラルのキー値での指定ができない。 |
createIndex(name, keyPath, unique) | keyPathで指定したプロパティを対象とするインデックスを作成する。uniqueで一意性を指定する。 | ○ | ○ | |
index(name) | インデックスを取得する。 | ○ | ○ | |
deleteIndex(indexName) | インデックスを削除する。 | ○ | ○ |
IDBIndex
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
name | インデックス名 | ○ | ○ | |
storeName | インデックスの対象とするオブジェクトストア名 | ○ | ○ | |
keyPath | インデックスの対象とするプロパティ名 | ○ | ○ | |
unique | 一意であるか | ○ | ○ | |
get(key) | key(リテラルのキー値または、IDBKeyRange)のデータを取得する。複数ある場合は、最初の1件を返す。 | △ | △ | ObjectStoreの同名のメソッドと同様。Firefoxの場合、インデックス経由の取得結果はキーとなる。 |
getKey(key) | getと同様だが、取得対象がキーとなる。 | △ | ○ | |
openCursor(range, direction) | range(リテラルのキー値または、IDBKeyRange)で指定された範囲のデータを取得する。directionで取得順序を指定する。(IDBCursor) NEXT: 昇順 NEXT_NO_DUPLICATE: 昇順(重複なし) PREV: 降順 PREV_NO_DUPLICATE: 降順(重複なし) |
△ | △ | ObjectStoreの同名のメソッドと同様。Firefoxの場合、インデックス経由の取得結果はキーとなる。 |
openKeyCursor(range, direction) | openCursorと同様だが、取得対象がキーとなる。 | △ | △ |
IDBCursor
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
direction | 走査方向 NEXT: 昇順 NEXT_NO_DUPLICATE: 昇順(重複なし) PREV: 降順 PREV_NO_DUPLICATE: 降順(重複なし) |
○ | ○ | |
key | カーソルを開くときに使用したキー | ○ | ○ | |
value | カーソルの現ポジションのデータ | ○ | △ | Firefoxの場合、インデックス経由では、キーとなる。 |
continue(key) | カーソルの現ポジションを次の走査方向へ進める。イベントが再発生し、カーソルの現ポジションが更新される。key(リテラルのキー値または、IDBKeyRange)を指定した場合、該当するキーまでカーソルを進める。 | △ | △ | Chrome、Firefoxともにkeyの指定は受け付けない。 |
delete() | カーソルの現ポジションのデータを削除する。 | ○ | ○ | |
update(value) | カーソルの現ポジションのデータをvalueで更新する。 | - | ○ |
IDBTransaction
メソッド・プロパティ | 説明 | コメント | ||
---|---|---|---|---|
mode | トランザクションの種類 READ_ONLY: 読込専用 READ_WRITE: 書込可能 VERSION_CHANGE: バージョン更新 |
○ | ○ | 以前あった、SNAPSHOT_READはなくなりました。 |
db | 接続しているデータベース。 | ○ | ○ | |
objectStore(name) | トランザクションの対象となるオブジェクトストアの中からnameで指定されたオブジェクトストアを取得する。 | ○ | ○ | |
abort() | トランザクションを中断し、ロールバックする。 | ○ | ○ | |
onabort | トランザクション中断時のコールバック | ○ | ○ | |
oncomplete | トランザクション完了時のコールバック | ○ | ○ | |
ontimeout | タイムアウト時のコールバック | - | - |
4. おわりに
indexDB は、まだまだ実装途中です。バージョンが変われば、そのままのコードでは動かなくなる可能性がかなり高いので、本格的なアプリを作れるようになるには、もう少し時間がかかるでしょう。それでも是非、試してみたいという方は、本エントリーを参考にして頂ければ幸いです。もちろん、(自称)html5-developers-jp indexedDB担当としては、今後もメンテナンスしていきたいと思いますので、ご指摘、ご質問等あればコメントでもMLでもツイッターでもお気軽にどうぞ!
まだまだ書くこともたくさんありますが(手元のサンプルコードもリファクタリングして公開しないといけないですし…)、まずはこのぐらいで。
@komasshu さんのこてさきAjax indexedDB を触ってみたも合わせてどうぞ