# 基本概念

# 用 Excel 类比数据库


在使用 IndexedDB 前,你首先需要懂基本的数据库概念


这里用 Excel 类比,简单介绍数据库的基本概念,不做太深入的讨论


需要了解四个基本概念,以关系型数据库为例


  • 数据库 Database
  • 数据表 Table(IndexedDB 中叫 ObjectStore)
  • 字段 Field
  • 事务 Transaction


(虽然 IndexedDB 算不上关系型数据库,但概念都是相通的)


假设清华和北大各自需要建一个数据库,用来存各自学生与教工的信息,假设命名为


  • 清华:thu
  • 北大:pku


这样,清北之间的数据就可以相互独立


然后,我们再到数据库里建表


  • student 表,储存学生信息
  • stuff 表,储存教工信息


数据表(Table)是什么?说白了,就是一个类似于 Excel 表一样的东西


比如 student 表,可以长这样:

image.png


上面的 学号、姓名、年龄、专业 就是数据表的字段


当我们想往 student 表添加数据时,就需要按照规定的格式,往表里加数据(关系型数据库的特点,而 IndexedDB 允许不遵守格式)


数据库也给我们提供了方法,当我们知道一个学生的学号(id),就可以在非常短的时间内,在表里成千上万个学生中,快速找到这个学生,并返回他的完整信息


也可以根据 id 定位,对该学生的数据进行修改,或者删除


id 这种每条数据唯一的值,就可以被用来做主键(primary key),主键在表内独一无二,无法添加相同主键的数据


增、删、改、查这些操作,都需要通过事务 Transaction 来完成


  • 如果事务中任何一个操作没有成功,整个事务都会回滚
  • 在事务完成之前,操作不会影响数据库
  • 不同事务之间不能互相影响


举个例子,当你发起一个事务,想利用这个事务添加两个学生,如果第一个学生添加成功,但是第二个学生添加失败,事务就会回滚,第一个学生将根本不会在数据库中出现过


事务在银行转账这种场景非常有用:如果转账中任何一步失败了,整个转账操作就和没发生过一样,不会造成任何影响


在同一个 Excel 文件(数据库)中,我们除了 student 表,还可以有 stuff 表(同一个数据库中有了两个不同的数据表):

image.png


然后,清华和北大各自分一个 Excel 文件,就相当于分了两个数据库

image.png


总而言之,不扯数据库各种难理解的概念,我们其实完全可以用 Excel 来类比数据库


  • 一个 Excel 文件就是一个 Database
  • 一个 Excel(Database)里可以有很多不同表格(数据表 Table)
  • 表格的列的名称其实就是字段


如果你是新手,用 Excel 类比理解数据库完全没问题,足以使用 IndexedDB 了


虽然说 IndexedDB 使用 key-value 的模式储存数据,但你也完全可以用数据表 Table 的模式来看待它



# 原生 IndexedDB 的使用


使用 IndexedDB 的第一步是打开数据库:


const request = window.indexedDB.open('pku'); 


上面这个操作打开了名为 pku 的数据库,如果不存在,浏览器会自动创建


然后 request 上有三个事件:


var db; // 全局 IndexedDB 数据库实例

request.onupgradeneeded = function (event) {
   db = event.target.result;
   console.log('version change');
};

request.onsuccess = function (event) {
   db = request.result;
   console.log('db connected')
};

request.onblocked = function (event) {
   console.log('db request blocked!')
}

request.onerror = function (event) {
   console.log('error!');
}; 


IndexedDB 有一个版本(version)的概念,连接数据库时就可以指定版本


const version = 1;
const request = window.indexedDB.open('pku', version); 


版本主要用来控制数据库的结构,当数据库结构(表结构)发生变化时,版本也会变化


如上,request 上有四个事件:


  • onupgradeneeded 在版本改变时触发
    • 注意首次连接数据库时,版本从 0 变成 1,因此也会触发,且先于 onsuccess
  • onsuccess 在连接成功后触发
  • onerror 在连接失败时触发
  • onblocked 在连接被阻止的时候触发,比如打开版本低于当前存在的版本


注意这四个事件都是异步的,意味着在连接 IndexedDB 的请求发出去后,需要过一段时间才能连上数据库,并进行操作


开发者对数据库的所有操作,都得放在异步连上数据库之后,这有的时候会带来很大的不便


而开发者如果想创建数据表(在 IndexedDB 里面叫做 ObjectStore),只能将其放到 onupgradeneeded 事件中(官方的定义是需要一个 IDBVersionChange 的事件)


request.onupgradeneeded = function (event) {
   db = event.target.result;
   if (!db.objectStoreNames.contains('student')) {
       db.createObjectStore('student', {
           keyPath: 'id', // 主键
           autoIncrement: true // 自增
       });
   }
} 


上面这段代码,在数据库初始化时,创建了一个 student 的表,并且以 id 为自增主键(每加一条数据,主键会自动增长,无需开发者指定)


在这一切做好以后,终于,我们可以连接数据库,然后添加数据了


const adding = db.transaction('student', 'readwrite') // 创建事务
   .objectStore('student') // 指定 student 表
   .add({ name: 'luke', age: 22 });

adding.onsuccess = function (event) {
   console.log('write success');
};

adding.onerror = function (event) {
   console.log('write failed');
} 


用同样的方法再加一条数据


db.transaction('student', 'readwrite')
   .objectStore('student')
   .add({ name: 'elaine', age: 23 }); 


然后,打开浏览器的开发者工具,我们就能看到添加的数据:

image.png


这个 key-value 结构对应的 Table 结构如下:

image.png


如果要获取数据,需要一个 readonly 的 Transaction


const request = db.transaction('student', 'readonly')
   .objectStore(this.name)
   .get(2); // 获取 id 为 2 的数据

request.onsuccess = function (event) {
   console.log(event.target.result) // { id: 2, name: 'elaine', age: 23 }
} 


综上,哪怕只是想简单的往 IndexedDB 里增加和查询数据,都需要写一大堆代码,操作非常繁琐,一不小心还容易掉坑里


这就是 GoDB 的设计初衷:将 IndexedDB 的操作大幅简化,在背后帮你把麻烦事解决