IndexedDB에 대해 알아보자

Kyeongmin·2021년 10월 18일
11

토이 프로젝트를 진행하기 위해 필요한 Web Database에 대해서 알아보던 중,
Local Storage와 IndexedDB에 대해서 알게 되었다.
진행하려는 토이 프로젝트에는 IndexedDB가 적합할 것 같아서, 조금 더 공부해보고 정리해보자!
📌 이 글의 모든 내용은 MDN Web Docs의 내용을 공부하며 정리한 글입니다.

IndexedDB란❓

IndexedDB는 파일이나 블롭 등 많은 양의 구조화된 데이터를 클라이언트에 저장하기 위한 로우 레벨 API입니다. (MDN Web Docs, IndexedDB)

Web Database의 종류 중에 많은 양의 데이터를 저장하기에 적합하다는 것이 내게 큰 특징으로 다가왔다. 이 외에 다른 특징들을 살펴보자면,

  1. 많은 양의 구조화된 데이터를 클라이언트에 저장할 수 있다.
    • 브라우저마다 편차는 있지만, 보통 HDD 용량의 50% 이다.
      (Local Storage의 용량은 겨우 10MB이다.)
  2. Javascript 기반의 객체지향 데이터베이스이다.
    • 즉, Javascript가 인식할 수 있는 자료형과 객체를 저장할 수 있다!
      (Local Storage는 String 형태만 저장할 수 있다.)
  3. 트랜잭션을 사용하며 Key-Value 데이터베이스이다.
  4. IndexedDB는 비동기 API이다.

IndexedDB 사용하기 💡

먼저 IndexedDB를 사용하는 기본패턴은 아래와 같다.
1. 데이터베이스를 열고
2. 객체 저장소(Object store)를 생성한 뒤
3. 트랜잭션(Transaction)을 시작하고 데이터베이스 작업(데이터 읽기/추가 등)을 요청한다.
4. DOM EventListner를 사용하여 요청이 완료될때가지 기다리고 결과를 확인한다.

그럼 이제 코드를 통해서 알아보자.

데이터베이스 열기

const request = indexedDB.open("notes", 2);
// IndexedDB.open(Name, Version)

request.onupgradeneeded = e => {
	alert("upgraed is called");}

request.onsuccess = e => {
	alert("success is called");}

request.error = e => {
	alert("error is called");}

IndexedDB.open(Name, Version) 함수를 통해서 DB open이 가능하다.
위의 세가지 케이스를 좀 더 자세히 살펴보자면,

  • onupgradeneeded
    Name 또는 Version과 일치하는 데이터베이스가 없는 경우 호출되며 데이터베이스를 생성한다.
  • onsuccess
    Name과 Version 모두 일치하는 데이터베이스가 있는 경우 호출된다.
  • error
    Name이 일치하지만 존재하는 DB의 Version 보다 낮은 Version을 호출하면 error가 발생한다.

Object Store 생성

request.onupgradeneeded = e => {                
	db = e.target.result;

	var objectStore = db.createObjectStore("memo", { keyPath: "id" });
}

createObjectStore(tableName) 함수를 이용하면 우리가 알고 있는 테이블을 만들 수 있다.
위의 코드처럼 onupgraedneeded 부분을 수정한 뒤 실행하면 notes 저장소에 personal_notes, todo_notes라는 테이블이 생성된다.

  • Index 설정하기
    앞서 특징을 설명할때 말했듯이 IndexedDB는 key-value DB이다.
    즉 key를 이용하여 자료를 검색해야한다는 한계가 있는데 이를 Index로 보완할 수 있다.
    value 내의 특정 객체를 Index로 지정한 뒤, Index로 원하는 value를 검색할 수 있다.
    Index를 만드는 방법은 아래와 같다.
request.onupgradeneeded = e => {
	// 중간 생략
objectStore.createIndex("name", "name", { unique: false });
}

createIndex()의 unique 속성을 이용하면 해당 객체를 unique key로 만들 수 있다.

데이터 읽기/추가/업데이트

  • 데이터 추가하기
    기본적으로 transaction() 함수로 transaction을 시작하고,
    objectStore() 함수로 테이블 선택 및 add() 함수로 원하는 객체를 추가한다.
    memo 객체저장소에 memos의 객체들이 추가된 것을 확인할 수 있다.
const memos = [
	{ id: 1, name: "Lee", age: 12, text:"I don't want to go to school."},
	{ id: 2, name: "Kim", age: 25, text:"I don't want to go to work." }
];

var memoObjectStore = db.transaction("memo", "readwrite").objectStore("memo");
memos.forEach(function(memo) {
	memoObjectStore.add(memo);
});

❗️ key 값이 같은 경우, ConstraintError를 발생시키며 해당 트랜잭션을 Abort 시킨다. 위의 예제에서는 key=id 이므로 id값을 동일한 값으로 설정시켜 실행하면 ConstraintError를 확인할 수 있다.

  • 데이터 읽기
    transaction()의 기본값은 readonly이기 때문에 생략했다.
    get()을 이용해 데이터를 읽는다.
var memoObjectStore = db.transaction("memo").objectStore("memo");
var request = memoObjectStore.get("1");

request.onerror = e => {
	alert("Error is called");
};

request.onsuccess = e => {
	alert(`Name: ${request.result.name}, Text: ${request.result.text}`);
}
  • 데이터 업데이트
    transaction()은 readwrite 모드로 설정해주고
    put()을 이용해 데이터를 업데이트 할 수 있다.
    해당 데이터를 다시 조회해보면 text가 변경됨을 확인 할 수 있다.
let memoObjectStore = db.transaction("memo", "readwrite").objectStore("memo");
let request = memoObjectStore.get("1");

request.onerror = e => {
  alert("Error is called");
};

request.onsuccess = e => {
  let data = request.result;
  data.text = "I don't want to travel";
  let requestUpdate = memoObjectStore.put(data);

  requestUpdate.onerror = e => {
    alert("Error is called");
  }

  requestUpdate.onsuccess = e => {
    alert("Success Updating");
  }
}

Cursor 사용하기

get()을 이용해 데이터를 조회하려면 key를 알고 있어야한다.
그렇다면 key를 모르는 상태에서 전체 데이터를 조회하려면? cursor를 이용하면 된다.

let objectStore = db.transaction("memo").objectStore("memo");
let request = objectStore.openCursor();

request.onerror = e => {
  alert("Error with opening cursor! " + e.target.error)
}

request.onsuccess = e => {
  let cursor = e.target.result;
  if(cursor){
    alert(`Key: ${cursor.key}, Name: ${cursor.value.name}, Text: ${cursor.value.text}`);
    cursor.continue();
  }

  else{
    alert("No more entries!");
  }
}

cursor를 이용해 다음 값을 조회하고 싶다면 cursor.continue()를 사용한다.

Mozila에서 비공식적으로 getAll()을 이용해 전체 값을 조회할 수 있음을 말하고 있는데, 이는 IndexedDB의 표준이 아니고 추후 없어질 가능성이 있기 때문에 권장하지 않고 있다.
getAll()은 전체 객체를 한번에 생성하기 때문에 전부 조회하는 용도로 사용해도 무방하나, 각 객체 또는 일부를 살펴보기에는 cursor 를 사용하는 것이 더 효율적이다.

profile
개발자가 되고 싶은 공장장이🛠

2개의 댓글

comment-user-thumbnail
2022년 7월 11일

좋은글에서 잘 배우고갑니다!

답글 달기
comment-user-thumbnail
2022년 11월 21일

잘 읽고 갑니다.

답글 달기