IndexedDB覚書とお試しメモ群
概要
JavaScript の IndexedDB を触ってみる。
DevTools の Console から遊べる✨
Database と ObjectStore の作成
- 下記スクリプトの流れ ( 実質、一括セット )
MyDatabaseName Hoge
という名前の DB を作成- 成功したら、オブジェクトストア
MyObjStr
を作成
( オブジェクトストア = いわゆるテーブル ) - 準備完了したら、オブジェクトを1つ突っ込む
( オブジェクト = いわゆるレコード )
{
// DBを開く(または初回作成)
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
// 作成時に呼ばれる。ちな、実質最初の一回だけみたいなモンで、
// 二回目からは「すでに作ってある」ので、基本的に呼ばれない
openRequest.onupgradeneeded=(e)=>{
const _db = e.target.result;
_db.onerror = (e)=>{
console.warn("DB読み込み失敗" , e);
}
// MyObjStr という名前のオブジェクトストア(テーブルみたいなモン?)を作成。
// このとき "id" という名前を持つキーをメインに据えた
/**
* @type IDBObjectStore
*/
const objectStore = _db.createObjectStore("MyObjStr" , { keyPath : "id"});
console.log(objectStore);
objectStore.transaction.oncomplete = function (_e) {
// 実質初回のみ
console.log("ObjectStore 作成完了");
}
// 【※】↓ 後で使います
//objectStore.createIndex("type" , "type" , { unique: true});
}
openRequest.onerror = (e)=>{
console._err(e);
}
// 準備完了時、ここが呼ばれる という認識で良い(ザル)
openRequest.onsuccess = (_e1)=>{
/**
* @type IDBDatabase
*/
const _iDB = _e1.target.result;
addSomeRecord(_iDB);
}
/** @type IDBDatabase */
function addSomeRecord(_iDb){
// 書き込み可の状態で開く
const _objStr = _iDb.transaction(["MyObjStr"], "readwrite").objectStore("MyObjStr");
const _addObj = { id : "hogehoge" , type : 0 ,memo : "メモ" };
// 上記オブジェクト(所謂レコード?)をデータベースへ追加
const _dbReq = _objStr.add( _addObj );
_dbReq.onsuccess = (_e)=>{
console.log(_e);
console.log(`新規登録に成功やで` , _addObj);
};
_dbReq.onerror = (_e)=>{
console.log("保存失敗 or 保存済");
};
}
}
上記スクリプトの諸々は 最初の一回のみ順当に処理され、2回目からは基本的に何も起こらない。
- 理由
- 1度目に呼んだ時の影響で、既に MyDatabaseName Hoge データベースは作成されており
- その中に MyObjStr オブジェクトストアも作成されており
- キー (id) が hogehoge のオブジェクト(所謂レコード)も登録されているから。
2回目からは.onerror
で「保存失敗」(既に登録済だから)のログが出てくるはず
DevTools の Application から、
[ DBごと / ObjectStore の中身ごと or オブジェクト単位 ] で削除できるので、
手動でDBを消しながら、都度試す のが良いと思います。
余談:Database バージョン番号について
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
第2引数部分は、そのDBのバージョン番号で、今後構成をアップグレードしたいときに増やす使い道があるのだとか。
( 自分は詳しく掘り下げてないのでノータッチです )
Index を増やす ( 更に unique で重複を防止する )
( ※ 作成済みのデータベースを、DevTools側で事前に削除しておいてください )
冒頭サンプルのコメントアウト部分を弄り、以下の処理を有効にしつつ、実行。
openRequest.onupgradeneeded=(e)=>{
// ...略...
// 【※】↓ 後で使います
+ objectStore.createIndex("type" , "type" , { unique: true});
// ...略...
};
MDN - IDBObjectStore: createIndex()
上記 .createIndex()
が有効になったスクリプトで作成されたオブジェクトストアは、
{ unique: true}
の影響を受け、同じ type
値を持つレコードが登録できなくなります。
- 以下3パターンによる違い ( 画像付き )
- type の Index をそもそも作成しない → 重複 OK
- type の Index を unique で作成した → 重複登録できない
- type の Index を unique 未指定で作成した → 重複 OK
もし今後 「type
が 1 のオブジェクトだけ○○したい」 のような意図があるなら、
unique のオプションはさておくとしても .createIndex()
を使った type 用の Index をこさえておくと良いかなと思います。
オブジェクトストア内アイテムを全回収(確認)
冒頭スクリプトにて、DBが一通り構成されている前提の話です。
DB読み出し成功時、 IDBObjectStore.getAll()
で中身を全部取得・確認します。
.getAll()
が成立すると、.onsuccess()
が呼ばれるので、そこで受け取る感じ。
ちなみに 回収した型はArray との事。
{
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onsuccess = (_e1)=>{
/**
* @type IDBDatabase
*/
const _iDB = _e1.target.result;
// ※ readonly で開いた
const _objStore = _iDB.transaction(["MyObjStr"]).objectStore("MyObjStr");
const _reqs = _objStore.getAll();
// 成功時
_reqs.onsuccess = (_e)=>{
if(_e.target.result !== undefined){
const _resArr = _e.target.result;
if(Array.isArray(_resArr)){
console.log("リスト取得成功");
console.log(_resArr);
return;
}
}
console.warn("取得失敗");
}
_reqs.onerror = (_err)=>{
console._err(_err);
}
}
}
.getAll()
の他に、 .get()
.getAllKeys()
.getKey()
等がある模様
任意キーを持つレコードを 1つ削除
IDBObjectStore.delete()
を使い、DBから 指定キー hogehoge を削除します
{
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onsuccess = (_e1)=>{
/**
* @type IDBDatabase
*/
const _iDB = _e1.target.result;
// ※ "readwrite" を指定する事
const _objStore = _iDB.transaction(["MyObjStr"] , "readwrite" ).objectStore("MyObjStr");
// 削除して💕
const _dbReq = _muteObjStr.delete( "hogehoge" );
_dbReq.onsuccess = (_e)=>{
console.log(_e);
console.warn("削除成功");
};
_dbReq.onerror = (_e)=>{
console.log(_e);
console.log("削除失敗 or 削除済");
};
}
}
範囲指定 IDBKeyRange を使う
デモにあたり、冒頭スクリプトのエラー・コメント表記等を削った 圧縮版 を用意しました
( ※ 事前にDBを削除してから実行してください )
DB構築~オブジェクト追加用スクリプト 【圧縮版】
{
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onupgradeneeded=(e)=>{
const _db = e.target.result;
const objectStore = _db.createObjectStore("MyObjStr" , { keyPath : "id"});
// ※ デモのため、本例では type に index を付けて構築
objectStore.createIndex("type" , "type" );
}
openRequest.onsuccess = (_e1)=>{
const _iDB = _e1.target.result;
addSomeRecords(_iDB);
}
function addSomeRecords(_iDb){
const _objStr = _iDb.transaction(["MyObjStr"], "readwrite").objectStore("MyObjStr");
_objStr.add( { id : "hogehoge" , type : 0 ,memo : "メモ" } );
_objStr.add( { id : "fugafuga" , type : 10 ,memo : "メモ" } );
_objStr.add( { id : "piyopiyo" , type : 100 ,memo : "メモ" } );
_objStr.add( { id : "mogemoge" , type : 10 ,memo : "メモ" } );
_objStr.add( { id : "poyopoyo" , type : 10 ,memo : "メモ" } );
}
}
上記を実行しなおすと、以下のようにずらっとレコードがセットされている筈。
( 更新しないと表示反映されない場合があるかも )
この状態から、更に以下のスクリプトで type == 10
にマッチするものをリストアップしてみます
{
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onsuccess = (_e1)=>{
const _iDB = _e1.target.result;
const _objStore = _iDB.transaction(["MyObjStr"] , "readwrite" ).objectStore("MyObjStr");
/** @type IDBIndex */
const _typeIndex = _objStore.index("type"); // type 用の Index を取り出す
// 条件 : 値 = 10 である
const _range = IDBKeyRange.only(10);
// その index 内で、条件に合う物を全て取得
const _query = _typeIndex.getAll( _range );
_query.onsuccess = (_e)=>{
const _result = _e.target.result;
console.log(_result);
}
}
}
条件を type >= 10
にしたいなら、下記の変更を加えてください。
- const _range = IDBKeyRange.only(10);
+ const _range = IDBKeyRange.lowerBound(10);
この様に IDBKeyRange
を使った、範囲条件付けメソッドが複数用意されています。
任意 Index が指定値以上のものをすべて削除
先程の DB & レコード構成 を対象として、
今度は type >= 10
に該当するオブジェクト(レコード)を削除してみます。
ちなみに cursor なる概念が新しく登場。
{
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onsuccess = (_e1)=>{
const _iDB = _e1.target.result;
const _transaction = _iDB.transaction(["MyObjStr"] , "readwrite" );
_transaction.oncomplete = (_e)=>{
console.log("処理(削除)完了");
}
const _objStore = _transaction.objectStore("MyObjStr");
/** @type IDBIndex */
const _typeIndex = _objStore.index("type"); // type 用の Index を取り出す
// 条件 : 値 >= 10 である
const _range = IDBKeyRange.lowerBound(10);
/** @type IDBRequest */
const _cursor = _typeIndex.openCursor( _range );
// 条件に合ったものを1つずつ .onsuccess で呼ぶ
_cursor.onsuccess = (_e)=>{
const _cursr = _e.target.result;
console.log(_cursr);
if(_cursr){
// 削除する
_cursr.delete();
// 次のレコードを扱う .onsuccess を呼ぶ …を繰り返す。
_cursr.continue();
}else{
// 削除できるレコードがなくなったので終わり
console.log("これ以上削除するものはありません");
}
}
}
}
削除対象がなくなるまでコールバック(.onsuccess()
)をブンブン回しつづける、ってのがポイントのようです。
個人的に、イテレータ的概念を連想しました。
削除完了のタイミングは _cursr
が 偽 になったタイミングより、
トランザクションの .oncomplete()
内部で判断させたほうが行儀良さそう。
日付指定でなんやかんやする
レコードに Date も突っ込めるらしい ので、日付系条件指定のレコード操作も書いてみました
作成される DB 構成も、冒頭スクリプトのものから変更しています
以下は、実行した当日を含み、さらに三日ずつ遡った Date オブジェクトを5つ登録する処理です。
{
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onupgradeneeded=(e)=>{
const _db = e.target.result;
const objectStore = _db.createObjectStore("MyObjStr" , { keyPath : "id"});
objectStore.createIndex("date" , "date" );
}
openRequest.onsuccess = (_e1)=>{
const _iDB = _e1.target.result;
addSomeRecords(_iDB);
}
function addSomeRecords(_iDb){
const _objStr = _iDb.transaction(["MyObjStr"], "readwrite").objectStore("MyObjStr");
const _step = 3;
const _now = new Date();
// [0,1,2,3,4] の配列を作成し、現在含む、三日ずつ遡った Date を登録
(Array.from({ length:5 },(_,i)=>i)).forEach((_e,_ind)=>{
const _date = new Date();
_date.setDate( _now.getDate() - ( _step *_ind) );
const _obj = { id : _ind, date : _date }
console.log( _obj );
_objStr.add( _obj );
});
}
}
このレコード群を対象に、7日以上前のレコードを全て削除する 下記処理をあてがってみます
{
// 7日前の date を持つレコードを削除
const openRequest = indexedDB.open("MyDatabaseName Hoge" , 1);
openRequest.onsuccess = (_e1)=>{
const _iDB = _e1.target.result;
const _transaction = _iDB.transaction(["MyObjStr"] , "readwrite" );
_transaction.oncomplete = (_e)=>{
console.log("処理(削除)完了");
}
const _objStore = _transaction.objectStore("MyObjStr");
/** @type IDBIndex */
const _dateIndex = _objStore.index("date"); // date 用の Index を取り出す
const _now = new Date();
const _targetDate = new Date();
_targetDate.setDate( _now.getDate() - 7 );
// 条件 : 値 <= (今から七日前の日付) である
const _range = IDBKeyRange.upperBound( _targetDate );
/** @type IDBRequest */
const _cursor = _dateIndex.openCursor( _range );
// 条件に合ったものを1つずつ .onsuccess で呼ぶ
_cursor.onsuccess = (_e)=>{
const _cursr = _e.target.result;
console.log(_cursr);
if(_cursr){
// 削除する
_cursr.delete();
// 次のレコードを扱う .onsuccess を呼ぶ …を繰り返す。
_cursr.continue();
}else{
// 削除できるレコードがなくなったので終わり
console.log("これ以上削除するものはありません");
}
}
}
}
[ 実行日(本日) , 3日前 , 6日前 , 9日前 , 12日前 ] と、5つあったレコードのうち、
[ 9日前 , 12日前 ] が削除されました。
今回、条件付けの範囲指定として IDBKeyRange.upperBound()
を用いています。