IndexedDBにはまってます。(その9)

IndexedDBにはまってます。(その8)では、プライマリキー(主キー)とキージェネレータの指定によるオブジェクトストアの基本利用を検証・比較しました。

まさに「はまった」感じでありました。

ここで、ざっくりと「まとめ」ておきます。(その8)では、取り留めなかったのですが、「まとめ」まで掲載すると原稿がさらに長くなるのでやめてました。今思えば、(その8)は四つぐらいに分けとけばよかったです。

(1)プライマリキー(主キー)の指定

  • プライマリキー(主キー)を指定しない場合は「アウトオブラインキー(out-of-line keys)」という運用モードになる。
  • プライマリキー(主キー)を指定した場合は「インラインキー(in-line keys)」という運用モードになる。
  • 前者ではプライマリキー(主キー)の運用がオブジェクトストアの外側で、後者は内側でなされる。また、前者ではadd()メソッドなどで第二パラメータを利用する。

(2)プライマリキー(主キー)の値

  • プライマリキー(主キー)には、数値と文字列、日付、配列を割り当てられる。

(3)キージェネレータの指定(真偽値)

  • オン(true)の場合:プライマリキー(主キー)は「単一キーワード(ピリオド区切は可)」もしくは「指定しない(null)」にて運用できる。
  • オフ(false)の場合:プライマリキー(主キー)は上のオンの場合以外に、「キーワードの配列(ピリオド区切は可)」にて運用できる。

本当にざっくり基本です。

キージェネレータのオンオフだけでなく、プライマリキー(主キー)の指定によるモード切り替えも絡んでくるので概念認識が厄介です。(その8)にある通り、妙なこともあります。また、これらの条件によって他の機能に影響もありそうです。

然るに、データベースの運用に至っては、まずは当たり障りのない指定に心がけることが肝要であると感じます。

はまってシリーズでは(その8)になって始めて、プライマリキー(主キー)を指定しない(null)の場合を紹介しました。add()メソッドの第二パラメータなんて自分でも失念してたくらいです。

指定に制限もありますし、自分が利用するとなると、プライマリキー(主キー)は明示的に運用した方が判りやすいと考えています。

よって、今後はプライマリキー(主キー)を指定して「インラインキー」モードにて検証を進め、紹介をすすめていきます。(ちゃぶ台返し?のないことを祈ります。)

キージェネレータをオフにして、プライマリキー(主キー)を指定する動作を検証していきます。

単一キーワードは予想がつくので、ちょっと違うキーワードのピリオド区切で指定をしていきます。動作にほぼ変わりはないと思っています。

つぎのようにオプションを指定します。

const Store_option = {keyPath:'member.id', autoIncrement:false};

サクサクと実行しましょう。

つぎのコードをWebブラウザ(Google Chromeを利用)のコンソールにコピー&ペーストし、エンターキーで実行して、まずはオブジェクトストアStore_nameが生成します。

const openRequest = indexedDB.open('DB_name');
openRequest.addEventListener('upgradeneeded', (event) => {
	const db = event.target.result;
	//const tx = event.target.transaction;
	console.log(`UPGRADE_DB_VER_>${db.version}_OLD_>${event.oldVersion}_NEW_>${event.newVersion}`);
	//オブジェクトストアのオプション指定
	const Store_option = {keyPath:'member.id', autoIncrement:false};
	try {
		//オブジェクトストア操作箇所
		const objectStore = db.createObjectStore('Store_name', Store_option);
		objectStore.transaction.addEventListener('complete', (event)=>{
			if(event.target.objectStoreNames.contains('Store_name')) {
				console.log(`オブジェクトストアを生成しました。`);
			}
		}, false);
		objectStore.transaction.addEventListener('abort', (event)=>{console.error(`トランザクションが中断しました。`);}, false);
		objectStore.transaction.addEventListener('error', (event)=>{console.error(`トランザクションでエラーが発生しました。`);}, false);
	} catch(exception) {
		console.error(`例外が発生しました。_>${exception.message}`);
	}
}, false);
openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開始しました。`);
	//db.close();
	db.addEventListener('close', ()=>{console.log(`データベースが閉鎖されました。`);}, false);
}, false);
openRequest.addEventListener('error', (event)=>{console.error(`データベースの開始に失敗しました。`);}, false);
コードを実行したコンソール画面(ピリオド区切)

オブジェクトストアが生成されました。

まずは試しにmember.idの指定を入れずに、次のようにレコードを格納してみます。

const storeRequest = objectStore.add({index:{en:1, sn:'SN0003'}, date:Date.now(), title:'ZINC', author:'hustlemouse', tag:['red','blue','yellow'], note:'Zn'});

実行します。

const openRequest = indexedDB.open('DB_name');
openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開始しました。`);
	if(db.objectStoreNames.contains('Store_name')) {
		const tx = db.transaction('Store_name', 'readwrite');
		tx.addEventListener('complete', (event)=>{console.log(`トランザクションが完遂しました。`);}, false);
		tx.addEventListener('error', (event)=>{console.error(`トランザクションでエラーが発生しました。`);}, false);
		tx.addEventListener('abort', (event)=>{console.error(`トランザクションが中断しました。`);}, false);
		const objectStore = tx.objectStore('Store_name');
		try {
			//レコード操作箇所
			const storeRequest = objectStore.add({index:{en:1, sn:'SN0003'}, date:Date.now(), title:'ZINC', author:'hustlemouse', tag:['red','blue','yellow'], note:'Zn'});
			storeRequest.addEventListener('success', (event)=>{console.log(`レコードの書き込みに成功しました。`);}, false);	
			storeRequest.addEventListener('error', (event)=>{console.error(`レコードの書き込みに失敗しました。`);}, false);
		} catch(exception) {
			console.error(`例外が発生しました。_>${exception.message}`);
		}
	} else console.error(`目的のオブジェクトストアはありませんでした。`);
	//db.close();
	db.addEventListener('close', ()=>{console.log(`データベースが閉鎖されました。`);}, false);
}, false);
openRequest.addEventListener('error', (event)=>{console.error(`データベースの開始に失敗しました。`);}, false);
コードを実行したコンソール画面(例外発生)

例外が発生して「Failed to execute ‘add’ on ‘IDBObjectStore’: Evaluating the object store’s key path did not yield a value.」とメッセージが返ってきます。「キーパスの値がないよー。」って感じ?です。

キージェネレータがオフなのですから、任意に指定する必要があります。

では、(その8)にもあった通り「member:{id:’hustle’}」と加えてレコードを格納します。(前のコロンを忘れないように…、)

プライマリキー(主キー)の値には連想配列が使えないのですが、使えてしまうとこのように階層構造のキーが使えなくなってしまうし、大混乱を招きかねません。当たりまえっちゃあー当たりまえですね。

const storeRequest = objectStore.add({index:{en:1, sn:'SN0003'}, date:Date.now(), title:'ZINC', author:'hustlemouse', tag:['red','blue','yellow'], note:'Zn', member:{id:2}});

実行します。

コードを実行したコンソール画面(レコード格納成功)
アプリケーション画面に現れたレコード(ピリオド区切)

レコードが格納されました。

では、試しに(その6)同様に「author:’hustlemouse’」を「author:’頑張る亜鉛ちゃん’」に変更して、add()メソッドで登録してみましょう。

const storeRequest = objectStore.add({index:{en:1, sn:'SN0003'}, date:Date.now(), title:'ZINC', author:'頑張る亜鉛ちゃん', tag:['red','blue','yellow'], note:'Zn', member:{id:2}});
コードを実行したコンソール画面(エラー発火)

はい、エラーが発火しました。

では、add()メソッドをput()メソッドに変更してみます。

const storeRequest = objectStore.put({index:{en:1, sn:'SN0003'}, date:Date.now(), title:'ZINC', author:'頑張る亜鉛ちゃん', tag:['red','blue','yellow'], note:'Zn', member:{id:2}});
コードを実行したコンソール画面(レコード格納成功)
変更が反映されたレコード(ピリオド区切)

レコードが格納されました。

それでは格納したレコードを取得するget()メソッドを使ってみましょう。

const storeRequest = objectStore.get(2);

「objectStore.get([2])」でも行けそうに思ってテストしてみましたがダメでした。階層構造があるにもかかわらず、単一キーワードと同じ扱いでした。

確認事項です。

いままではオブジェクトストアの操作でしたから「readwrite」を指定してましたが、読み込みだけなので「readonly」で必要十分です。

また、読み込みの成功が発火したイベント受け取り枠内で、コンソールに取得レコードを表示する命令行を挿入してます。

console.dir(event.target.result);

つぎのコードをWebブラウザのコンソールにコピー&ペーストし、エンターキーで実行します。

const openRequest = indexedDB.open('DB_name');
openRequest.addEventListener('success', (event)=>{
	const db = event.target.result;
	console.log(`データベースを開始しました。`);
	if(db.objectStoreNames.contains('Store_name')) {
		//オブジェクトストアの操作モードは'readonly'
		const tx = db.transaction('Store_name', 'readonly');
		tx.addEventListener('complete', (event)=>{console.log(`トランザクションが完遂しました。`);}, false);
		tx.addEventListener('error', (event)=>{console.error(`トランザクションでエラーが発生しました。`);}, false);
		tx.addEventListener('abort', (event)=>{console.error(`トランザクションが中断しました。`);}, false);
		const objectStore = tx.objectStore('Store_name');
		try {
			//レコード操作箇所
			const storeRequest = objectStore.get(2);
			storeRequest.addEventListener('success', (event)=>{
				console.log(`レコードの読み込みに成功しました。`);
				//取得レコードを表示
				console.dir(event.target.result);
			}, false);	
			storeRequest.addEventListener('error', (event)=>{console.error(`レコードの読み込みに失敗しました。`);}, false);
		} catch(exception) {
			console.error(`例外が発生しました。_>${exception.message}`);
		}
	} else console.error(`目的のオブジェクトストアはありませんでした。`);
	//db.close();
	db.addEventListener('close', ()=>{console.log(`データベースが閉鎖されました。`);}, false);
}, false);
openRequest.addEventListener('error', (event)=>{console.error(`データベースの開始に失敗しました。`);}, false);
コードを実行したコンソール画面(レコード取得成功)

最後にdelete()メソッドでレコードを削除します。面倒なので「レコードの書き込み」メッセージのまま「レコード操作箇所」を書き換えます。

ご注意、レコード取得コードを使って書き換えると「readonly」モードになってしまうので削除できません。

const storeRequest = objectStore.delete(2);

getに同じく「objectStore.delete([2])」でではなく単一キーワードと同じ扱いでした。

コードを実行したコンソール画面(レコード削除成功)

アプリケーション画面でレコードの削除が確認できます。

プライマリキー(主キー)階層構造を意識するだけのことで、ほぼ予想通りの挙動かと思います。一意に「member.id」が運用されること、お忘れ無く。

つづけて、プライマリキー(主キー)にキーワードの配列による指定となりますが、切りがよいので今回はここまでとします。そかさ。