前の記事
前回記事はこちら
基本パターン
- データベースを開く
- データベース内に、オブジェクトストアを作成する
- データベース操作のトランザクションを開始して、リクエストを行う
- 適切なDOMイベントを受け取ることにより、操作が完了するのを待つ
- 結果に応じた処理を行う
ストアを作成および構築する
- 接頭辞を使用している実装は不具合がある、未完成、古い版の仕様に従っている可能性があるため、製品版で使用することは推奨されない。対応しているものとするより、未対応とする方が良い。
if (!window.indexedDB) { console.log("このブラウザは安定版のIndexedDB を対応していません。") }
データベースを開く
// データベースは開く var request = window.indexedDB.open("MyTestDatabase", 3);
- データベースを開くリクエストは、すぐにはデータベースを開いたりトランザクションを開始したりはしない。
open()
関数を呼び出すと、結果(成功)またはイベントとして扱うエラー値を伴うIDBDatabase
オブジェクトを返す。
IDBFactory.open - Web API | MDN
ハンドラーの生成
- 全てが成功すると、
type
プロパティが”success"
であるDOM
イベントが、request
をtarget
として発生する。イベントが発生するとrequest
のonsuccess()
関数が、success
イベントを引数として呼び出される。 - 何らかの問題がある場合、
type
プロパティが”error"
であるDOM
イベントがrequest
で発生する。これは、エラーイベントを引数としてonerror()
関数を呼び出す。 IndexedDB AP
Iはエラー処理を最小限にするよう設計されているが、データベースを開く場合、データベースを作成する許可をユーザーがウェブアプリに与えていない場合が多い。- ブラウザは
web
アプリが初めてストレージ用にIndexedDB
を開こうとした時に、ユーザーへプロンプトを表示する。ユーザーはアクセスを許可または否定できる。
データベースを作成またはデータベースのバージョンを更新する
- 新しいデータベースを作成したり既存のデータベースのバージョンを更新したりすると、
onupgradeneeded
イベントが発生して、request.result
に設定したonversionchange
イベントハンドラーにIDBVersionChangeEvent
オブジェクトが渡される。 upgradeneeded
イベントのハンドラーでは、このバージョンのデータベースで必要なオブジェクトストアを作成する。- onupgradeneededイベントから正常に抜けた場合は、データベースを開くリクエストのonsuccessハンドラーが実行される。
データベースを構築する
- 値をオブジェクトストアへ保存するたびに、値がキーと関連づけられる。オブジェクトストアで
キーパス
(オブジェクトストアやインデックスのどこからブラウザがキーを取り出すべきかを定義する)を使用するかキージェネレータ
(指定した順序で新たなキーを背制する仕組み)を使用するかに応じて、キーを供給する方法がいくつか存在する。 - オブジェクトストアがプリミティブではなくオブジェクトを保持していれば、オブジェクトストアでインデックスを作成することもできる。
- インデックスは、オブジェクトのキーではなく保存されたオブジェクトのプロパティの値を使用して、オブジェクトストア内に保存された値を検索することを可能にする。
- さらにインデックスには、保存されたデータに単純な制限を強制する機能がある。
// データを保存例 const dbName = "the_name"; var request = indexedDB.open(dbName, 2); request.onerror = function(event) { // エラー処理 }; request.onupgradeneeded = function(event) { var db = event.target.result; // 顧客の情報を保存する objectStore を作成する。 // "ssn" は一意であることが保証されているので、キーパスとして使用する。 // 第一引数:ストアの名前 // 第二引数:引数オブジェクト(省略可能)。 // keyPath:ストア内で個々のオブジェクトを一意にするプロパティ var objectStore = db.createObjectStore("customers", { keyPath: "ssn" }); // 顧客を名前で検索するためのインデックスを作成。 // 重複する可能性があるので、一意のインデックスとしては使用できない。 objectStore.createIndex("name", "name", { unique: false }); // 顧客をメールアドレスで検索するためのインデックスを作成します。2 人の顧客が同じメールアドレスを // 使用しないようにしたいので、一意のインデックスを使用します。 objectStore.createIndex("email", "email", { unique: true }); // データを追加する前に objectStore の作成を完了させるため、 // transaction oncomplete を使用します。 objectStore.transaction.oncomplete = function(event) { // 新たに作成した objectStore に値を保存します。 var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers"); customerData.forEach(function(customer) { customerObjectStore.add(customer); }); }; };
onupgradeneeded
はデータベースの構造を変えることができる唯一の場所で、オブジェクトストアの作成や削除、インデックスの構築および削除が可能。
キージェネレータを使用する
- オブジェクトストアを作成するときにautoIncrementフラグを設定すると、そのオブジェクトストアでキージェネレータを使用できる。
- キージェネレータを使用すると、オブジェクトストアに値を追加するのに応じて自動的にキーが生成される。
- データベースの操作が取り消された場合を除いて、キージェネレーターの現在の値が減少することはない。
データの追加、読み取り、削除
- データベースで何かを行えるようにする前に、トランザクションを開始しなければならない。
- トランザクションはデータベースオブジェクトから生じており、トランザクションの対象にしたいオブジェクトストアを指定しなければならない。
- トランザクションは、
readonly
,readwrite
,versionchange
の3つのモードを使用できる。- データベースの「スキーマ」や構造を変更するには、
versionchange
モードにしなければならない。このトランザクションは、version
を指定してIDBFactory.open
メソッドを呼び出すことによって開く。 - 既存のオブジェクトストアからレコードを読み出すには、トランザクションで
readoly
モードまたはreadwrite
モードを使用できる。 - 変更処理を行うには、
readwrite
モードにしなければならない。 - このようなトランザクションは、
IDBDatabase.transaction
で開き、第一引数はstoreNames
(アクセスしたいオブジェクトストアの配列で定義されるスコープ)で第二引数はトランザクションのmode
(readonly
またはreadwrite
)。 - 適切なトランザクションのスコープとモードによって、データアクセスを高速化できる。
- データベースの「スキーマ」や構造を変更するには、
データベースにデータを追加する
- トランザクションは、イベントループととても密接に結びついている。
- トランザクションを作成して、それを使用せずにイベントループに戻ると、トランザクションが非アクティブ状態になる。
- トランザクションをアクティブにし続ける唯一の方法が、トランザクションでリクエストを行うこと。
- リクエストが完了すると、DOMイベントが発生して、リクエストが成功したと仮定すれば、コールバックの実行中にトランザクションを拡張することができる。
- トランザクションを拡張せずにイベントループへ戻ると、トランザクションは非アクティブ状態になる。
- 保留中のリクエストがある限り、トランザクションはアクティブ状態であり続ける。
- トランザクションは
error
,abort
,complete
の3種類のDOMイベントを受け取る可能性がある。
カーソルの使用
- オブジェクトストア内の全ての値を取得したい場合は、
カーソル
を使用できる。 - opneCursor()関数は、引数がいくつかある。
- 第一引数:すぐに取得するキーレンジオブジェクトを使用して、読み出すアイテムの範囲を制限できる。
- 第二引数:反復処理を行いたい方向を指定できる。
カーソル
の成功イベントのコールバックは少し特殊で、カーソルオブジェクト自体は、リクエストのresult
だが、実際のキーと値は、カーソルオブジェクトのkey
プロパティとvalue
プロパティで見つかる。- 進み続けたい場合は、カーソルで
continue()
を呼び出す必要がある。 - データの終端に達した(または、
openCursor()
リクエストに一致する項目が存在しない)場合は成功のコールバックを受け取るが、result
プロパティがundefined
になる。- それ以外に、このような処理を行うために
getAll()
(およびgetAllKeys()
)を使用することができるが、カーソルのvalue
プロパティに関してパフォーマンスコストが発生する。例えば、それぞれのキーを検索することにのみ関心がある場合は、getAl()
よりもカーソルを使用する方が効率的。オブジェクトストア内の全データの配列を得ようとする場合は、getAll()
を使用すべき。
- それ以外に、このような処理を行うために
インデックスの使用
- 検索するとき、正しいものが見つかるまでデータベース内の全ての値に対して反復処理を行わなければならない。この方法だと遅いため、代わりにインデックスを使用する。
- 指定した値に該当する全ての項目にアクセスしなければならない場合は、カーソルを使用する。インデックス上で、2種類のカーソルを開くことができる。
ノーマルカーソル
は、インデックスのプロパティと、オブジェクトストア内のオブジェクトを紐付ける。キーカーソル
はインデックスのプロパティと、オブジェクトストア内にオブジェクトを保存するために使用するキーを紐づける。
var index = objectStore.index("name"); // 顧客レコードのオブジェクト全体を得るために、ノーマルカーソルを使用する。 index.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // cursor.key は "Bill" のような名前、cursor.value はオブジェクト全体。 console.log("Name: " + cursor.key + ", SSN: " + cursor.value.ssn + ", email: " + cursor.value.email); cursor.continue(); } }; // 顧客レコードのオブジェクトのキーを得るために、キーカーソルを使用。 index.openKeyCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // cursor.key は "Bill" のような名前、cursor.value は SSN 。 // 保存されたオブジェクトの他の部分を直接取得する方法はない。 console.log("Name: " + cursor.key + ", SSN: " + cursor.value); cursor.continue(); } };
カーソルの範囲や方向を指定する
- カーソルで参照する値の範囲を制限したい場合は、
IDBKeyRange
オブジェクトを使用して、openCursor()
またはopenKeyCursor()
の第1引数として渡す - 一つのキーのみ許可するキーレンジ、下限または上限の片方を持つキーレンジ、あるいは下限と上限の両方を持つキーレンジを作成できる。
- 境界は
closed
(キーレンジは指定した値を含む)またはopen
(キーレンジは指定した値を含まない) - 方向の切り替えは、openCursor()の第2引数に
prev
を渡す
// "Donna" にのみ一致 var singleKeyRange = IDBKeyRange.only("Donna"); // "Bill" より先のすべてに一致。"Bill" を含みます。 var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill"); // "Bill" より先のすべてに一致。ただし "Bill" は含まない。 var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true); // "Donna" までのすべてに一致。ただし "Donna" は含まない。 var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true); // "Bill" から "Donna" までに一致。ただし "Donna" は含まない。 var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true); // いずれかのキーレンジを使用するには、openCursor()/openKeyCursor() の第 1 引数として渡す。 index.openCursor(boundKeyRange).onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // 一致した場合の処理。 cursor.continue(); } }; objectStore.openCursor(boundKeyRange, "prev").onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // 項目に対して行う処理 cursor.continue(); } };
// データを保存例 const dbName = "the_name"; var request = indexedDB.open(dbName, 2); request.onerror = function(event) { // エラー処理 }; request.onupgradeneeded = function(event) { var db = event.target.result; // 顧客の情報を保存する objectStore を作成する。 // "ssn" は一意であることが保証されているので、キーパスとして使用する。 // 第一引数:ストアの名前 // 第二引数:引数オブジェクト(省略可能)。 // keyPath:ストア内で個々のオブジェクトを一意にするプロパティ var objectStore = db.createObjectStore("customers", { keyPath: "ssn" }); // 顧客を名前で検索するためのインデックスを作成。 // 重複する可能性があるので、一意のインデックスとしては使用できない。 objectStore.createIndex("name", "name", { unique: false }); // 顧客をメールアドレスで検索するためのインデックスを作成。2 人の顧客が同じメールアドレスを // 使用しないようにしたいので、一意のインデックスを使用する。 objectStore.createIndex("email", "email", { unique: true }); // データを追加する前に objectStore の作成を完了させるため、 // transaction oncomplete を使用。 objectStore.transaction.oncomplete = function(event) { // 新たに作成した objectStore に値を保存。 var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers"); customerData.forEach(function(customer) { customerObjectStore.add(customer); }); }; transaction.onerror = function(event) { // エラー制御 }; }; // 「スキーマ」や構造の変更 var transaction = db.transaction(["customers"], "readwrite"); // レコードの読み出し var transaction = db.transaction(["customers"], "readonly"); // 変更処理 var transaction = db.transaction(["customers"], "readwrite"); // データの削除 var request = db.transaction(["customers"], "readwrite") .objectStore("customers") .delete("444-44-4444"); request.onsuccess = function(event) { // 削除完了! }; // データの取得 var request = db.transaction(["customers"], "readwrite") .objectStore("customers") .get("444-44-4444"); request.onerror = function(event) { // エラー処理! }; request.onsuccess = function(event) { // request.result に対して行う処理 console.log("Name for SSN 444-44-4444 is " + request.result.name); }; // データの更新 request.onsuccess = function(event) { // 更新したい、古い値を取得 var data = request.result; // オブジェクト内の値を、希望する値に更新 data.age = 42; // 更新したオブジェクトを、データベースに書き戻す。 var requestUpdate = objectStore.put(data); requestUpdate.onerror = function(event) { // エラーが発生した場合の処理 }; requestUpdate.onsuccess = function(event) { // 成功 - データを更新 }; }; // カーソルを使用したデータの取得 objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // 処理 cursor.continue(); } } // インデックス // 最初に、 request.onupgradeneeded の中にインデックスを生成したか確認する。 // objectStore.createIndex("name", "name"); // まだであれば、 DOMException が発生する。 var index = objectStore.index("name"); index.get("Donna").onsuccess = function(event) { console.log("Donna's SSN is " + event.target.result.ssn); }; ``` # ウェブアプリが別のタブで開かれているときにバージョンを変更する ```javascript var openReq = mozIndexedDB.open("MyTestDatabase", 2); openReq.onblocked = function(event) { // 他のタブがデータベースを読み込んでいる場合は、処理を進める前にそれらを閉じる。 console.log("このサイトを開いている他のタブをすべて閉じてください!"); }; openReq.onupgradeneeded = function(event) { // 他のデータベースはすべて閉じられたため、すべての処理を行う。 db.createObjectStore(/* ... */); useDatabase(db); }; openReq.onsuccess = function(event) { var db = event.target.result; useDatabase(db); return; }; function useDatabase(db) { // 別のページがバージョン変更を求めた場合に、通知されるようにするためのハンドラーを追加。 // データベースを閉じなければならない。データベースを閉じると、別のページがデータベースをアップグレードできる。 // これを行わなければ、ユーザーがタブを閉じるまでデータベースはアップグレードされない。 db.onversionchange = function(event) { db.close(); console.log("新しいバージョンのページが使用可能になりました。再読み込みしてください!"); }; // データベースを使用する処理 }
セキュリティ
- 同一生成元の原則。ストアとサイトの生成元を紐づけるため、他の生成元からアクセスできない