냐냐한 IT/냐냐한 실습 기록

IndexedDB API: IndexedDB 사용 - 2 (저장소(store) 생성 및 구조화)

소소하냐 2022. 11. 9. 17:29
MDN 원문 참조: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB#creating_and_structuring_the_store 
날짜 : 2022.11.09 (문서 내용은 계속 변경되는 부분이라 정리한 날짜를 함께 기록)

 

위 참조 링크 내용을 정리하였습니다. 


[ 저장소(store) 생성 및 구조화 (Creating and structuring the store) ] 

하위 목차 

- 데이터베이스 열기(Opening a database)

- 핸들러 생성 (Generating handlers)

- 에러 처리 (Handling Errors)

- 데이터베이스 생성 또는 버전 업데이트

- 데이터베이스 구조화 (Structuring the database)

- 키 생성기 사용 (Using a key generator)

 

데이터베이스 열기(Opening a database)  

프로세스 시작 : 

// database 열기
const request = window.indexedDB.open("데이터베이스명", 버전);

- 데이터베이스 open은 다른 작업처럼 "요청(request)"해야 함. 

 

- open 요청으로 데이터베이스를 열거나, 즉시 트랜잭션을 시작하지 않음. 

- open() : IDBOpenDBRequest 반환 (이벤트로 처리하는 result(success) 또는 error 와 함께)

- IndexedDB의 대부분의 비동기 함수는 같은 작업을 함 - result 또는 error와 함께 IDBRequest 반환 

- open 함수의 result는 IDBDatabase의 인스턴스. 

open 요청 및 반환 타입 및 내용 확인

- 버전 : 데이터베이스의 object store와 구조  schema를 결정 

    - 데이터베이스 없을 경우 : open으로 생성 > onupgradeneeded 트리거 > 여기서 데이터베이스 schema생성

    - 데이터베이스 있을 경우 : 업그레이드 된 버전 번호가 지정 > onupgradeneeded 트리거 > 여기서 업데이트된 schema를 제공 

open 시 데이터베이스 존재/미존재/버전업그레이드/오류 발생 시 트리거 되는 이벤트 확인

- 자세한 내용은 아래의 데이터베이스 생성 또는 버전 업데이트 IDBFactory.open(원문) 페이지 참고

 

*Warning* : 

- 버전 번호는 unsigned long long number 

- float 사용 하지말 것. 
  ( float을 사용하면 가장 가까운 낮은 integer로 변환되어 트랜잭션이 시작되지 않거나, upgradeneeded 이벤트가 트리거 되지 않을 수 있음. 예로 const request = indexedDB.open("MyTestDatabase", 2.4); // 2로 반올림되므로 이렇게 사용하지 말 것

 

핸들러 생성 (Generating handlers) 

- 생성한 거의 모든 요청의 첫 번째 할 일은 success, error handler를 추가하는 것 

request.onerror = (event) => {
  // request.errorCode로 작업
};
request.onsuccess = (event) => {
  // request.result로 작업
};

- 성공 : event는 "success" type의 DOM event, targetrequest를 함께 발생.  onsuccess(event) 트리거 됨

- 오류 : event는 "error" type의 DOM event, request에 발생. onerror(event) 트리거 됨, 

  (정리자 덧, "success"이벤트와 같이 target에 request를 함께 보내는 것으로 아래 이미지처럼 확인 됨. 이벤트가 아닌 open request 의 반환  IDBOpenDBRequest 에 error 속성이 있어서 이렇게 쓴 것인가라는 추정) 

 

위 내용은 원문 내용이 이해가 되지 않아 나름 이해한대로 정리했지만, 혹시 몰라 원문(더보기 클릭)도 함께 넣습니다. 

더보기

 If everything succeeds, a success event (that is, a DOM event whose type property is set to "success") is fired with request as its target. Once it is fired, the onsuccess() function on request is triggered with the success event as its argument. Otherwise, if there was any problem, an error event (that is, a DOM event whose type property is set to "error") is fired at request. This triggers the onerror() function with the error event as its argument.

원문 설명이 이해가 되지 않아서 확인용

- 데이터베이스를 열 때, error 이벤트를 생성하는 몇가지 일반적인 조건 중 가장 많은 문제 : 데이터베이스 생성 권한 

- 브라우저는 웹 응용프로그램이 저장을 위해 IndexedDB를 열려고 할 때 사용자에게 메세지를 보냄. 사용자가 접근 허용/거부를 선택  
  (정리자 덧.  크롬에서 확인 시 현재(2022.11) 권한 관련 메세지는 동작하지 않는 것으로 추정. 크롬 설정의 권한에도 해당 권한 없음)

- 브라우저의 개인 정보 보호 모드에서의 IndexedDB storage는 시크릿 세션이 종료될 때까지 메모리 내에서만 지속 됨. 

  (Firefox에서는 2021년 5월 현재 구현되지 않았으므로 Firefox Private Browsing에서 IndexedDB를 전혀 사용할 수 없습니다.)

 

- 생성이 성공했다는 가정하에, 다음에 할 내용은 다음 코드와 같을 것입니다 : 

var db; 
var request = indexedDB.open("MyTestDatabase"); // 생성
request.onerror = function(event) {
  alert("Why didn't you allow my web app to use IndexedDB?!");
};
request.onsuccess = function(event) {
  db = request.result; // 반환된 IDBDatabase을 db 변수에 할당하여 나중에 사용할 수 있도록 함
};

에러 처리 (Handling Errors) 

- 에러 이벤트는 버블링 됨. 

- 에러가 발생한 요청에서, 트랜잭션으로 버블링 된 다음, 마지막으로 데이터베이스 객체로 버블링 됨 

- 모든 요청에 error handler 를 처리하지 않으려면, 다음과 같이 데이터베이스 객체에 single error handler를 대신 추가 : 

db.onerror = (event) => {
  // 이 데이터베이스의 request들을 대상으로 하는 모든 error에 대한 일반 오류 처리기
  console.error(`Database error: ${event.target.errorCode}`);
};

- 데이터베이스를 열 때, 일반적으로 발생하는 에러 중 하나인 VER_ERR 

  (저장된 데이터베이스의 버전이 열려고 하는 버전보다 높은 것을 의미. error handler에 의해 항상 처리되야할 오류의 한 경우) 

 

데이터베이스 생성 또는 버전 업데이트 

- 새 데이터베이스를 생성하거나 기존 데이터베이스의 버전을 높일 때 

  : onupgradeneeded 이벤트가 트리거되고, IDBVersionChangeEvent object가 request.result(예, 예제의 db)에 설정된 onversionchange event handler로 전달 됨 

upgradeneeded 이벤트 핸들러에서, 데이터베이스의 버전에 필요한 object store를 생성해야 합니다 : 

// 이 이벤트는 최신 브라우저에서만 구현되었습니다. 
request.onupgradeneeded = (event) => {
  // IDBDatabase interface 저장
  const db = event.target.result;

  // 이 데이터베이스에 objectStore 생성
  const objectStore = db.createObjectStore("name", { keyPath: "myKey" });
};

- 데이터베이스의 이전 버전에서 object store 생성했으면, 동일한 object store를 다시 만들 필요가 없음 

   : 이미 존재하는 이름으로 object store를 생성(또는 존재하지 않는 이름의 object store를 삭제)하려고 하면 error가 발생 

- 새 object store를 생성 또는 더 이상 필요하지 않은 이전 버전의 object store를 삭제 

- 기존 object store를 변경해야할 경우(예: keyPath 변경), 이전 object store를 삭제해야만 새로운 옵션으로 다시 생성 가능 

  (주의. 이렇게 하면 object store의 정보가 삭제됨. 해당 정보를 저장하려면, 데이터베이스를 업그레이드하기 전에 해당 정보를 읽고 다른 곳에 저장해야 함) 

onupgradeneeded 이벤트가 성공적으로 종료되면, open database request의  onsuccess handler가 트리거 됨.

 

 

 

데이터베이스 구조화 (Structuring the database) 

이제 데이터베이스를 구성합니다. 

- IndexedDB는 테이블이 아닌 object store를 사용

- 하나의 데이터베이스에는 여러개의 object store가 포함 가능 

- 값(데이터)이 object store에 저장될 때, key와 연결(associate)

- object store가 key path / key generator 사용에 따라 key가 제공되는 여러 방법이 존재

 

key가 제공되는 여러 방법을 보여주는 표 

Key Path
(keyPath)
Key Generator
(autoIncrement)
설명
No No - 숫자, 문자열과 같은 원시(primitive) 값을 포함, 모든 종류의 값(value)을 보유 가능
- 새로운 값(value)을 추가할 때 마다 별도의 key argument를 제공
Yes No - JavaScript object만 보유 가능
- object에는 key path와 같은 이름의 property 필요
No Yes - 모든 종류의 값을 보유 가능. 
- key는 자동으로 생성, 또는 특정 key를 사용려면 별도의 key argument 제공 
Yes Yes - JavaScript object만 보유 가능
- 키가 생성되고, 생성된 키의 값은 key path와 같은 이름의 property의 object에 저장
- 이미 있는 property라면 새 키를 생성하는 대신 해당 property의 값이 키로 사용  

 

- object store에 index 생성 가능 (primitive 값은 불가, object 값은 가능) 

   - index를 사용하면, object의 key 대신 property의 값으로 조회 가능 

   - index에는 간단한 제약(constraint) 적용 가능 (예: unique로 데이터 중복을 막음)


예제로 개념 설명. 예제에서 사용할 customer 데이터를 먼저 정의 : 

const customerData = [
  { ssn: "444-44-4444", name: "Bill", age: 35, email: "bill@company.com" },
  { ssn: "555-55-5555", name: "Donna", age: 32, email: "donna@home.org" }
];

 

데이터를 저장할 IndexedDB를 만드는 방법 : 

const dbName = "the_name";

const request = indexedDB.open(dbName, 2);

request.onerror = (event) => {
  // 에러 처리
};
request.onupgradeneeded = (event) => {
  const db = event.target.result;

  // objectStore를 생성하여 customers 정보를 보유
  // key path로 "ssn"을 사용 uique가 보장된다는 전제 조건)
  const objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

  // name으로 customers 검색할 수 있는 index를 생성
  // 중복이 있을 수 있으므로 unique 사용 안함
  objectStore.createIndex("name", "name", { unique: false });

  // email로 customer를 검색할 수 있는 index를 생성 
  // 두 고객이 같은 email을 가지지 않도록 unique 사용
  objectStore.createIndex("email", "email", { unique: true });

  // 데이터를 추가하기 전에 objectStore 생성이 완료되었는 지 확인하려면 transaction oncomplete를 사용 
  objectStore.transaction.oncomplete = (event) => {
    // 새로 생성된 objectStore에 값을 저장 
    const customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
    customerData.forEach((customer) => {
      customerObjectStore.add(customer);
    });
  };
};

- onupgradeneeded는 데이터베이스의 구조(structure)를 수정할 수 있는 유일한 위치 

   - 여기서, object store를 생성, 삭제하고 index를 생성, 제거 가능

 

- object store는 createObjectStore() 호출로 생성 : store의 이름과 parameter object(optional)를 사용 

 

- 위 예제 설명 : 

   - "customers"라는 object store를 요청, keyPath를 "ssn"으로 지정하여 개별 object를 고유하게 해줌

   - "ssn"은 objectStore에 저장되는 모든 object에 필수 

   - 저장된 object의 name property를 찾는 "name"이라는 index를 요청 

   - createObjectStore() 처럼, createIndex()는 index의 유형을 구체화하는 object(optional)를 사용

   - name property가 없어도, object 추가는 성공, 해당 object는 "name" 인덱스에는 나타나지 않음

 

이제 우리는 object store에서 바로 ssn을 사용하거나, 인덱스 name을 사용하여 저장된 customer 객체들을 검색이 가능. 더 자세한 내용은, 인덱스 사용하기(원문) 섹션을 참고

 

키 생성기 사용 (Using a key generator) 

- object store를 생성할 때 autoIncrement 플래그를 설정하면 해당 object store에 key generator가 활성화 : 기본값은 비활성 

- key generator를 사용하면, object store에 값을 추가하면 자동으로 key가 생성 

- 처음 시작 번호는 항상 1, 새로 자동 생성된 key는 이전 key를 기준으로 1씩이 증가 

- key generator의 현재 번호는 감소되지 않음. (데이터베이스 트랜잭션이 중단 등, 데이터베이스 작업이 되돌려진(rever) 결과 제외) 

- object store에서 record를 삭제 또는 모든 record를 지워도 object store의 key generator에 영향을 주지 않음 

 

아래와 같이 key generator로 다른 object store("names")를 생성 가능 : 

// indexedDB 열기
const request = indexedDB.open(dbName, 3);

request.onupgradeneeded = (event) => {

  const db = event.target.result;

  // autoIncrement를 true로 설정한 "names"라는 다른 object store를 생성
  const objStore = db.createObjectStore("names", { autoIncrement : true });

  // "names" object store는 key generator를 갖기 때문에, name 값에 대한 key가 자동으로 생성됩니다. 
  // 추가된 record는 다음과 같습니다: 
  // key : 1 => value : "Bill"
  // key : 2 => value : "Donna"
  customerData.forEach((customer) => {
    objStore.add(customer.name);
  });
};

key generator에 대한 더 자세한 내용은 "W3C Key Generators"를 참고 

 

위 내용들을 확인하기 위해 사용한 코드 및 설명 

- 새 탭(google) 에서 개발자 모드 콘솔로 실행함. 

- 같은 세션에서 버전업데이트 시에는 정상 동작하지 않아서, 세션 닫고(해당 탭 닫은 후 새로 열기) 실행 시 정상 동작했음 

  (Exception : DOMException: Failed to read the 'error' property from 'IDBRequest': The request has not finished.) 

  ==> 웹 응용프로그램내에서는 이런 현상이 없었기에, 개발자 모드 콘솔에서 실행에서 발생하는 문제로 추측 

function openDB(version) {    
  console.log(`-------------- openDB(${version}) --------------`)  
  const request = indexedDB.open("testDBNew", version);
  request.onerror = function (event) {
      console.log("onerror!", event);
  };
  request.onsuccess = function (event) {
      console.log("onsuccess!", event);
  };
  request.onupgradeneeded = function (event) {
      console.log("onupgradeneeded!", event);
  };
}
// ------------ 사용 -------------------------
// openDB(1);
// openDB(2); 
// ...

 


IndexedDB 정리 목록

IndexedDB API: Intro (개요)

IndexedDB API: IndexedDB 사용 - 1 (개요)

IndexedDB API: IndexedDB 사용 - 2 (저장소(store) 생성 및 구조화)

IndexedDB API: IndexedDB 사용 - 3 (데이터 추가, 검색, 제거)

IndexedDB API: IndexedDB 사용 - 4 (버전 변경 시 다른 탭, 보안, 브라우저 종료 시 경고 등 나머지 내용)

IndexedDB API: IndexedDB 주요 특징 및 기본 용어