前端数据库IndexedDB与Dexie

842 阅读4分钟

1.介绍

webSQL

早期web浏览器器使用webSQL,采用的是关系型数据库,由于底层使用sqlLite,多个浏览器不好统一标准。

缺点:

  1. 查询效率低
  2. 前端需要学习sql语言
  3. 基于表的描述没有对象直观
  4. 读写都需要从表转化为json对象,或json转表操作
  5. 存储空间有限5m-25m

IndexedDB

基于NoSql的非关系型数据库,跟mongodb类似。基于json schema, 由于web比较流行的也是json格式,所以更倾向与非关系型数据库。

优点:

  1. 很适合前端或浏览器读取和存储
  2. 支持任意类型的对象结构
  3. 存储容量大,据说是当前浏览器可以使用容量的20%,即浏览器如果分配了1g,它就有200mb

缺点:

  1. 建立连接与操作事务,代码繁琐
  2. 都是异步回调,会有回调地狱问题
  3. 高级查询自己实现复杂

Dexie

基于IndexedDB的开源库

优点:

  1. 封装好了建立连接,事务,常用增删改。
  2. 简化的创建表特殊写法
  3. 封装的复杂的高级查询方法
  4. 支持索引

例子

1.IndexedDB

创建+新增


var request = window.indexedDB.open('myDB', '1') //创建或修改数据库版本, 第二个参数 为版本号

request.onerror = (event) => { //操作失败
    console.log(event)
}
request.onsuccess = (event) => { //操作成功
    var db = event.target.result
    // 数据读取
    var memberObjectStore = db.transaction('member').objectStore('member')
    var userRequest = memberObjectStore.get(1)
    userRequest.onsuccess = function (event) {
        console.log(event.target.result)
    }
} 
request.onupgradeneeded = (event) => { //数据库连接后的回调方法
    var db = event.target.result 
    //创建表
    var objectStore = db.createObjectStore('member', { keyPath: 'id' })  //id为自增id
    objectStore.createIndex('name', 'name', { unique: false }) //unique 设置为不唯一
    objectStore.createIndex('gender', 'gender', { unique: false }) 
    //新增
    var data = [{  id: 1,   name: 'jason',  gender: '男'}, 
           {  id: 2,   name: 'bob',  gender: '女'},  ]
    objectStore.transaction.oncomplete = () => { //启动事务 
        var memberObjectStore = db.transaction('member', 'readwrite').objectStore('member')
        data.forEach(data => { //遍历保存
            memberObjectStore.add(data)
        })
    } 
} 

在谷歌浏览器-application - Storage - IndexedDB 查看保存的数据 image.png

查询

request.onsuccess = (event) => { //操作成功
    var db = event.target.result
    // 数据读取
    var memberObjectStore = db.transaction('member').objectStore('member')
    var userRequest = memberObjectStore.get(1)
    userRequest.onsuccess = function (event) {
        console.log(event.target.result)
    }
} 

删除

request.onsuccess = (event) => { //操作成功
    var db = event.target.result 
   let id = 1
   db.transaction(["member"],"readwrite") .objectStore("member") .delete(id);
} 

修改

//let id = 1
var transaction = db.transaction(["member"],"readwrite");
var objectStore = transaction.objectStore("member");
var request = objectStore.get(id);
request.onsuccess = function(event){
    request.result.name = '李四';
    objectStore.put(request.result);
};

2.Dexie

官方文档:dexie.org/docs/API-Re…

创建

var db = new Dexie("MyDatabase");
db.version(1).stores({
    friends: "++id, &uid, name, age, *tags", //   ++id : 主键(自增) , &uid: 唯一id 
    gameSessions: "id, score"
});

更新版本

db.version(1).stores({
    friends: "++id,name,age,*tags",
    gameSessions: "id,score"
});
db.version(2).stores({
    friends: "++id, [firstName+lastName], yearOfBirth, *tags", // Change indexes
    gameSessions: null // Delete table

}).upgrade(tx => {
    // Will only be executed if a version below 2 was installed.
    return tx.table("friends").modify(friend => {
        friend.firstName = friend.name.split(' ')[0];
        friend.lastName = friend.name.split(' ')[1];
        friend.birthDate = new Date(new Date().getFullYear() - friend.age, 0);
        delete friend.name;
        delete friend.age;
    });
});

对象映射

class Friend {
    // Prototype method
    save() {
        return db.friends.put(this); // Will only save own props.
    }

    // Prototype property
    get age() {
        return moment(Date.now()).diff (this.birthDate, 'years');
    }
}
db.friends.mapToClass(Friend);

新增

await db.friends.add({name: "Josephine", age: 21});

await db.friends.bulkAdd([
  {name: "Foo", age: 31},
  {name: "Bar", age: 32}
]);

修改

await db.friends.put({id: 4, name: "Foo", age: 33});
await db.friends.bulkPut([
    {id: 4, name: "Foo2", age: 34},
    {id: 5, name: "Bar2", age: 44}
]);
await db.friends.update(4, {name: "Bar"});
await db.customers
    .where("age")
    .inAnyRange([ [0, 18], [65, Infinity] ])
    .modify({discount: 0.5});

删除

await db.friends.delete(4);

await db.friends.bulkDelete([1,2,4]);

const oneWeekAgo = new Date(Date.now() - 60*60*1000*24*7);
await db.logEntries
    .where('timestamp').below(oneWeekAgo)
    .delete();

查询

const someFriends = await db.friends
    .where("age").between(20, 25)
    .offset(150).limit(25)
    .toArray();
    
    
await db.friends
    .where("name").equalsIgnoreCase("josephine")
    .each(friend => {
        console.log("Found Josephine", friend);
    });
    
    
const abcFriends = await db.friends
    .where("name")
    .startsWithAnyOfIgnoreCase(["a", "b", "c"])
    .toArray();

版本号

  1. 每次指定的版本都会记录到浏览器
  2. 浏览器什么时候都只能保存一份数据库
  3. 下次再重新指定版本号时,只有大于之前的版本号才能更新,否则更新失败。如原来是1.5, 现在改成1.1 则浏览器无法读取。(即如果以前有1.1版本,也不能再追溯了)
  4. upgrade方法会在版本号变化并且大于之前才会触发,如浏览器上次记录1.5,最新是1.7,则会触发更新方法,并且更新方法执行完后,把当前浏览器版本更新为 1.7
  5. 每次更新版本,都建议在更新方法执行清空表的数据。
DB.version(1.7).stores({ 
  friends: "++id, &uid, name, age, *tags"
}).upgrade (trans => { 
    console.log('版本升级后,清空数据库内容成功,防止字段类型不一致')  
    trans.storeNames.forEach(o => {
        trans[o].toCollection().delete() //清空数据
    }) 
});

条件+分页查询

data(){
    return {
      form: {
        content: "",
        data: "",
        view_path: "",
        router_meta: "",
        createTimeStart: "",
        createTimeEnd: "",
        user_code: "",
        user_name: "",
      }
   }
},
methods: {
  let dbTable 
  let queryObj = {}
  try {
     //设置from过滤条件
     Object.keys(this.form).forEach(key => {
      if(this.form[key] ) {
        if(key != "createTimeStart" && key != "createTimeEnd"){
          Object.assign(queryObj,{[key]:this.form[key]} )
        }
      }
    })

    let list = []

    if (Object.keys(queryObj).length > 0) {
      dbTable = dbTable.where(queryObj) //
    }
    let isfilterDate = false
    if (this.form.createTimeStart || this.form.createTimeEnd) {
      isfilterDate = true
    }
    const offset = (pageNo - 1) * pageSize
    if (isfilterDate) { //当包含日期过滤时候的特殊处理
      if (Object.keys(queryObj).length == 0) {
        dbTable = dbTable.where("id").aboveOrEqual(0) //当没有过滤条件也要加入where 包装and能够正常执行
      }
      dbTable.and(item => {
        if (!item.create_time) return true
        let flag = this.form.createTimeStart ? item.create_time.getTime() > this.form.createTimeStart.getTime() : true
        let flag2 = this.form.createTimeEnd ? item.create_time.getTime() < this.form.createTimeEnd.getTime() : true
        return this.form.createTimeEnd && this.form.createTimeEnd ? flag && flag2 : (flag || flag2)
      })
    }
    this.tables[this.moduleType].total = await dbTable.count()
    list = await dbTable.offset(offset).limit(pageSize).toArray()
    list = list.map((o) => {
      o.create_time_show = formatDate(o.create_time)
      return o
    })
    this.tables[this.moduleType].rows = list
  } catch (error) {
    this.$messageWarn("查询异常", error)
    this.loading = false
  }
}

indexedDB存储容量与地址

容量例子:

256GB 的硬盘,should remain available的值就是2GB,也就是浏览器临时存储空间是 254GB。这时候临时存储空间已经用了 4GB 了,这时候 IndexedDB 可用大小就是 50GB。

存放地址

windows:

C:\Users\xxxx\AppData\Local\Google\Chrome\User Data\Default\IndexedDB