html5存储:indexedDB数据库使用

IndexedDB 是一个为了能够在客户端存储可观数量的结构化数据,并且在这些数据上使用索引进行高性能检索的 API。虽然 DOM 存储 对于存储少量数据是非常有用的,但是它对大量结构化数据的存储就显得力不从心了。IndexedDB 则提供了这样的一个解决方案。
IndexedDB 分别为同步和异步访问提供了单独的 API 。同步 API 本来是要用于仅供 Web Workers 内部使用,但是还没有被任何浏览器所实现。异步 API 在 Web Workers 内部和外部都可以使用。所以现在可以使用的基本就是异步API了,因为在h5手机项目中用到了indexedDB存储app的相关数据,所以写一些实例供参考:

1.异步API
首先了解一下什么是异步 API:异步 API 方法调用完后会立即返回,而不会阻塞调用线程。要异步访问数据库,需要要调用 window 对象 indexedDB 属性的 open() 方法。该方法返回一个 IDBRequest 对象 (IDBOpenDBRequest);异步操作通过在 IDBRequest 对象上触发事件来和调用程序进行通信。IDBOpenDBRequest接口定义了几个重要属性:

  • onerror: 请求失败的回调函数句柄
  • onsuccess:请求成功的回调函数句柄
  • onupgradeneeded:请求数据库版本变化句柄

通过不同回调函数可以实现数据库调用成功,失败和升级的方法,下面从打开数据库开始。

2.创建(打开)数据库
以最简单的例子来说,创建一个数据库至少要有数据库名称和数据库版本两个参数,为了方便,我们创建一个简单的数据库管理类:

class DBmanager {
    //数据库管理类的构造函数,包含两个构造参数,数据库名称和数据库版本,数据库版本如果不指定默认为1;
    constructor(DBname, DBversion) {
        this.DBname = DBname;
        this.DBversion = DBversion || 1;
        this.currentDB = null; //当前数据库
    }
    //打开(没有创建过则创建)数据库的方法
    openDB() {
        //调用API打开数据库
        var request = window.indexedDB.open(this.DBname, this.DBversion);
        //打开数据库失败的回调
        request.onerror = function (e) {
            console.log(e.currentTarget.error.message);
        }.bind(this);
        //打开数据库成功的回调
        request.onsuccess = function (e) {
            this.currentDB = e.target.result;
            console.log(this.currentDB.name + ' database is already opened!');
        }.bind(this);
        //数据库升级的回调
        request.onupgradeneeded = function (e) {
            console.log('database version is already upgrade to '+this.DBversion);
        }.bind(this);
    }
}

其中onupgradeneeded回调会在两种情况下调用:

  1. 数据库第一次创建的时候
  2. 数据库版本变化的时候

调用onupgradeneeded时,onupgradeneeded的执行会在onsuccess和onerror之前,因为在打开数据库时,先检测是否有版本变化,如果版本有变化则先升级数据库再打开,所以数据库的升级方法我们可以在onupgradeneeded里实现。
为了看到具体的执行效果,我们运行一下:

window.onload = function () {
    var db = new DBmanager('testDB', 1);
    db.openDB();
}

在chromium浏览器中运行,按F12可以看到在APPlication选项下的indexedDB中有了我们刚刚新建的数据库“testDB”,版本为1,log为打开数据库成功时的回调打出的log,说明数据库已经成功打开:
这里写图片描述

3.关闭和删除数据库
关闭数据库可以直接调用数据库对象的close方法,我们给DBmanager中添加一个成员函数:

//关闭数据库的方法
closeDB() {
    this.currentDB.close();
    console.log(this.currentDB.name + ' database is already closed!');
}

删除数据库使用indexedDB对象的deleteDatabase方法, 我们给DBmanager中添加一个成员函数:

//删除数据库的方法
deleteDB() {
    window.indexedDB.deleteDatabase(this.DBname);
    console.log(this.DBname + ' database is already deleted!');
}

由于是异步API,不能保证在closeDB方法调用通过openDB获取到DB对象,所以我们在这里用setTimeout延时一下,保证获取到DB对象后再关闭数据库。

window.onload = function () {
    var db = new DBmanager('testDB', 1);
    db.openDB();
    setTimeout(function () {
        db.closeDB();
        db.deleteDB();
    }, 500);
}

打出的log可以运行后在console看到

4.object store
在关系型数据库中,我们把数据以表的形式存储,但在indexedDB中没有表的概念,而是object store,一个数据库中可以有多个object store,一个object store相当于一个表,可以存储多种数据类型,每条数据都对应一个Key值,我们可以指定某个字段作为Key值,也可以自动生成Key值,也可以指定。比如下面的例子,我们在onupgradeneed里新建一个名为“students”的object store,指定”id”为key值。

request.onupgradeneeded = function (e) {
    console.log('database version is already upgrade to '+this.DBversion);
    this.currentDB = e.target.result;
    if(!this.currentDB.objectStoreNames.contains('students')){
        //指定一个键为主键
        this.currentDB.createObjectStore('students',{keyPath:"id"});
        //指定为主键自增模式
        //db.createObjectStore('students',{autoIncrement: true});
    }
}.bind(this);

5.事务transaction
在关系型数据库中也有事务的概念,但是这里的事务和关系型数据库中的事务不太一样,在indexedDB中,在对数据库数据进行增删改查数据之前,都需要先开始一个事务,事务中指定需要那些object store进行操作,获取到对应的object store后我们便可以对获取到的object store进行操作,在这里我们给DBmanager中添加一个成员函数,用来根据object store的名称来开启一个事务,获取对应的object store:

//根据storeName获取对应store的方法
getStoreByName(storeName){
    var transaction = this.currentDB.transaction(storeName,'readwrite');
    return transaction.objectStore(storeName);
}

其中事务拥有三种模式:

  1. 只读:read,不能修改数据库数据,可以并发执行
  2. 读写:readwrite,可以进行读写操作
  3. 版本变更:verionchange

6.添加数据
我们在调用createObjectStore方法时已经创建了一个名为students的object store,并指定主键为id,现在我们可以向store中添加数据了,我们准备一些数据:

//名为students的ObjectStore
const datas=[{
    id:11,
    name:"zhangsan",
    age:24
},{
    id:12,
    name:"lisi",
    age:30
},{
    id:13,
    name:"wangwu",
    age:26
},{
    id:14,
    name:"zhaoliu",
    age:26
}];

之后我们给DBmanager添加一个用于添加数据的成员函数:

//添加数据的方法
addData(storeName,data){
    var store = this.getStoreByName(storeName);
    for(var i = 0; i<data.length ; i++){
        store.add(data[i]);
    }
}

调用:

window.onload = function () {
    var db = new DBmanager('testDB', 2);
    db.openDB();
    setTimeout(function () {
        db.addData('students',datas);
        db.closeDB();
    }, 500);
}

打开chromium浏览器,我们可以在indexedDB中看到我们添加的数据:
这里写图片描述

7.删除数据
删除一条数据:

//删除一条数据(根据主键key(id))
deleteDataByKey(storeName,key){
    var store = this.getStoreByName(storeName);
    store.delete(key);
}

删除所有数据:

//删除整个ObjectStore的数据
deleteAllData(storeName){
    var store = this.getStoreByName(storeName);
    store.clear();
}

是否删除成功可以运行后在chromium浏览器进行验证

8.更新数据
这里我们写一个根据key值更新age值的方法供参考:

//根据键值修改数据
updateDataByKey(storeName,key,age){
    var store = this.getStoreByName(storeName);
    var request = store.get(key);
    request.onsuccess = function (e) {
        var student = e.target.result;
        student.age = age;
        store.put(student);
    }
}

调用:

window.onload = function () {
    var db = new DBmanager('testDB', 1);
    db.openDB();
    setTimeout(function () {
        db.updateDataByKey('students',11,25);
        db.closeDB();
    }, 500);
}

这样就把名为students的store里的id为11的那条数据对应的age值修改为25.

9.查找数据
根据id值将对应的name和age查询出来:

//根据键值查询数据
getDataByKey(storeName,key){
    var store = this.getStoreByName(storeName);
    var request = store.get(key);
    request.onsuccess = function (e) {
        var student = e.target.result;
        console.log('key is '+key+',name:'+student.name+',age:'+student.age);
    }
}

10.索引
以上介绍了indexedDB的基本使用,接下来我们介绍一下indexedDB的索引,索引的好处是可以快速定位数据,提高搜索速度。在indexedDB中有两种索引,一种是自增长的int值,由indexedDB自己定义;另一种是keyPath,由我们自己指定,重点来看看keyPath方式索引的使用

11.建立索引
可以在创建object store的时候指明索引,使用object store的createIndex创建索引,有三个参数

  • 索引名称
  • 索引属性字段名
  • 索引属性值是否唯一

例:我们为name和age两个属性建立对应的索引,可以将onupgradeneeded写为下面的方式:

request.onupgradeneeded = function (e) {
    console.log('database version is already upgrade to '+this.DBversion);
    this.currentDB = e.target.result;
    if(!this.currentDB.objectStoreNames.contains('students')){
        //指定一个键为主键
        var store = this.currentDB.createObjectStore('students',{keyPath:"id"});
        //指定为主键自增模式
        //db.createObjectStore('students',{autoIncrement: true});
        store.createIndex('nameIndex','name',{unique:true});
        store.createIndex('ageIndex','age',{unique:false});
    }
}.bind(this);

索引建立成功后在chromium可以看到对应的索引:
这里写图片描述

12.根据索引查询数据

//根据索引查询数据
getDataByIndex(storeName,indexName,index){
    var store = this.getStoreByName(storeName);
    var indexStore = store.index(indexName);
    var request = indexStore.get(index);
    request.onsuccess = function (e) {
        var student = e.target.result;
        console.log('index is '+index+',id:'+student.id+',age:'+student.age);
    }
}

例如我们要查询nameIndex为zhangsan的那条数据:

db.getDataByIndex('students','nameIndex','zhangsan');

13.游标cursor
在indexedDB中使用索引和游标是分不开的,如果查询结果是多个数据,我们就可以使用游标来遍历查询结果了:

//通过游标获取所有数据
fetchStoreByCurser(storeName) {
    var store = this.getStoreByName(storeName);
    var request = store.openCursor();
    request.onsuccess = function (e) {
        var cursor = e.target.result;
        if (cursor) {
            var currentStudent = cursor.value;
            console.log('currentStudent:' + currentStudent.name);
            //curson.contine()会使游标下移,直至没有数据则返回undefined
            cursor.continue();
        }
    }
}

14.游标和索引结合查询

//通过游标和index查询
getMultipleData(storeName, indexName, age) {
    var store = this.getStoreByName(storeName);
    var indexStore = store.index(indexName);
    var request = indexStore.openCursor(IDBKeyRange.only(age));
    request.onsuccess = function (e) {
        var cursor = e.target.result;
        if (cursor) {
            var student = cursor.value;
            console.log(student.name);
            cursor.continue();
        }
    };
}

例如我们要查询所有age为26的数据:

db.getMultipleData('students', 'ageIndex', 26);

15.指定游标范围
index.openCursor()/index.openKeyCursor()方法在不传递参数的时候会获取object store所有记录,像上面例子一样我们可以对搜索进行筛选
可以使用key range 限制游标中值的范围,把它作为第一个参数传给 openCursor() 或openKeyCursor()

  • IDBKeyRange.only(value):只获取指定数据
  • IDBKeyRange.lowerBound(value,isOpen):获取最小是value的数据,第二个参数用来指示是否排除value值本身,也就是数学中的是否是开区间
  • IDBKeyRange.upperBound(value,isOpen):和上面类似,用于获取最大值是value的数据
  • IDBKeyRange.bound(value1,value2,isOpen1,isOpen2):获取位于 value1和
    value2之间的数据, isOpen1和 isOpen2也是是否开区间,值为true或者false,

举个例子:

//指定游标范围查询
getDataBetweenTwoData(storeName,indexName,start,end,isStartOpen,isEndOpen) {
    var store = this.getStoreByName(storeName);
    var indexStore = store.index(indexName);
    //true是不包括,false是包括
    var request = indexStore.openCursor(IDBKeyRange.bound(start, end, isStartOpen, isEndOpen));
    request.onsuccess = function (e) {
        var cursor = e.target.result;
        if (cursor) {
            var student = cursor.value;
            console.log(student.name);
            cursor.continue();
        }
    }
}

比如查询age在24和30之间的所有数据,包括24但不包括30,可以这么查:

db.getDataBetweenTwoData('students','ageIndex',24,30,false,true);

16.获取某个store中存储条目的总数

getStoreDataCount(storeName) {
    var store = this.getStoreByName(storeName);
    var req = store.count();
    req.onsuccess = function (e) {
        console.log(e.target.result);
    }
}

17.获取store中满足多个条件的条目的总数
在多个条件的前提下查询我们首先要有一个多条件查询的索引。由于数据有限,我们将创建一个由name和age组成的多条件索引,在onupgradeneeded中,为‘students 创建一个索引:

store.createIndex('myIndex',['name','age'],{unique:false});

之后我们写一个函数:

//获取store中满足多个条件的条目的总数
getDataCountByMultiCondition(storeName,indexName,condition) {
    var store = this.getStoreByName(storeName)
    var indexStore = store.index(indexName);
    var request = indexStore.openCursor(IDBKeyRange.only(condition));
    request.onsuccess = function (e) {
        var cursor = e.target.result;
        if (cursor) {
            var student = cursor.value;
            console.log(student.id);
            cursor.continue();
        }
    }
}

然后查询所有名为“lisi”,年龄为30的数据:

db.getDataCountByMultiCondition('students','myIndex',['lisi',30]);

18.如何获取到返回值
在上面的例子中,我们所获取到的数据都是用在函数中用log打印出结果,但是在实际中,我们需要将查询到的结果获取到其他地方,但是在request的回调里我们是无法直接将结果作为函数返回值直接return给函数调用者的,这里也要使用回调将获取到的值传递出去,比如我们重写获取某个store中存储条目的总数这个方法:

//获取某个store中存储条目的总数
fetchStoreDataCount(storeName,callback) {
    var count = 0;
    var store = this.getStoreByName(storeName);
    var req = store.count();
    req.onsuccess = function (e) {
        count = e.target.result;
        callback(count);
    }
}

其中callback为回调函数,我们将返回值用callback传递出去:

db.fetchStoreDataCount('students',function (count) {
    console.log(count);
});

用这种方法,我们可以将任意查询获得的值用callback传递出去,可以试下用这个方法将上面所有查询到的数据传递出去,只需要在调用的地方实现callback函数即可

19.其他
有些同学可能注意到了一个地方,在openDB的方法中,request的onsuccess,onerror以及onupgradeneeded后都加了.bind(this),原因如下:
因为我用的是ES6中的class来实现了整个DBmanager,但是js中的this并不完全和java中的this一样指的就是当前对象,比如说这里:request的onsuccess,onerror以及onupgradeneeded回调函数中的this指的并不是DBmanager对象,而是指request,所以为了在onsuccess中改变DBmanager改变成员变量的值,所以将指向DBmanager的this值bind到onsuccess中。
除此之外,在DBmanager实现过程中,为了方便并没有用get,set方法去获取和设置成员变量的值,而且很多地方还需进一步改进,这里只是给各位一个简单的参考,欢迎批评指正。
20.源代码
indexedDB.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    class DBmanager {
        //数据库管理类的构造函数,包含两个构造参数,数据库名称和数据库版本,数据库版本如果不指定默认为1;
        constructor(DBname, DBversion) {
            this.DBname = DBname;
            this.DBversion = DBversion || 1;
            this.currentDB = null; //当前数据库
        }

        //打开(没有创建过则创建)数据库
        openDB() {
            //调用API打开数据库
            var request = window.indexedDB.open(this.DBname, this.DBversion);
            //打开数据库失败的回调
            request.onerror = function (e) {
                console.log(e.currentTarget.error.message);
            }.bind(this);
            //打开数据库成功的回调
            request.onsuccess = function (e) {
                this.currentDB = e.target.result;
                console.log(this.currentDB.name + ' database is already opened!');
            }.bind(this);
            //数据库升级的回调,此回调在数据库版本变化时和数据库第一次创建时执行
            request.onupgradeneeded = function (e) {
                console.log('database version is already upgrade to ' + this.DBversion);
                this.currentDB = e.target.result;
                if (!this.currentDB.objectStoreNames.contains('students')) {
                    //指定一个键为主键
                    var store = this.currentDB.createObjectStore('students', {keyPath: "id"});
                    //指定为主键自增模式
                    //db.createObjectStore('students',{autoIncrement: true});
                    store.createIndex('nameIndex', 'name', {unique: false});
                    store.createIndex('ageIndex', 'age', {unique: false});
                    store.createIndex('myIndex', ['name', 'age'], {unique: false});
                }
            }.bind(this);
        }

        //关闭数据库
        closeDB() {
            this.currentDB.close();
            console.log(this.currentDB.name + ' database is already closed!');
        }

        //删除数据库
        deleteDB() {
            window.indexedDB.deleteDatabase(this.DBname);
            console.log(this.DBname + ' database is already deleted!');
        }

        //根据storeName获取对应store的方法
        getStoreByName(storeName) {
            var transaction = this.currentDB.transaction(storeName, 'readwrite');
            return transaction.objectStore(storeName);
        }

        //添加数据
        addData(storeName, data) {
            var store = this.getStoreByName(storeName);
            for (var i = 0; i < data.length; i++) {
                store.add(data[i]);
            }
        }

        //根据键值修改数据
        updateDataByKey(storeName, key, age) {
            var store = this.getStoreByName(storeName);
            var request = store.get(key);
            request.onsuccess = function (e) {
                var student = e.target.result;
                student.age = age;
                store.put(student);
            }
        }

        //根据键值查询数据
        getDataByKey(storeName, key) {
            var store = this.getStoreByName(storeName);
            var request = store.get(key);
            request.onsuccess = function (e) {
                var student = e.target.result;
                console.log('key is ' + key + ',name:' + student.name + ',age:' + student.age);
            }
        }

        //根据索引查询数据
        getDataByIndex(storeName, indexName, index) {
            var store = this.getStoreByName(storeName);
            var indexStore = store.index(indexName);
            var request = indexStore.get(index);
            request.onsuccess = function (e) {
                var student = e.target.result;
                console.log('index is ' + index + ',id:' + student.id + ',age:' + student.age);
            }
        }

        //通过游标获取所有数据
        fetchStoreByCurser(storeName) {
            var store = this.getStoreByName(storeName);
            var request = store.openCursor();
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    var currentStudent = cursor.value;
                    console.log('currentStudent:' + currentStudent.name);
                    //curson.contine()会使游标下移,直至没有数据则返回undefined
                    cursor.continue();
                }
            }
        }

        //通过游标和index查询
        getMultipleData(storeName, indexName, age) {
            var store = this.getStoreByName(storeName);
            var indexStore = store.index(indexName);
            var request = indexStore.openCursor(IDBKeyRange.only(age));
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    var student = cursor.value;
                    console.log(student.name);
                    cursor.continue();
                }
            };
        }

        //指定游标范围查询
        getDataBetweenTwoData(storeName, indexName, start, end, isStartOpen, isEndOpen) {
            var store = this.getStoreByName(storeName);
            var indexStore = store.index(indexName);
            //true是不包括,false是包括
            var request = indexStore.openCursor(IDBKeyRange.bound(start, end, isStartOpen, isEndOpen));
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    var student = cursor.value;
                    console.log(student.name);
                    cursor.continue();
                }
            }
        }

        //获取某个store中存储条目的总数
        getStoreDataCount(storeName) {
            var store = this.getStoreByName(storeName);
            var req = store.count();
            req.onsuccess = function (e) {
                console.log(e.target.result);
            }
        }

        //获取store中满足多个条件的条目的总数
        getDataCountByMultiCondition(storeName, indexName, condition) {
            var store = this.getStoreByName(storeName)
            var indexStore = store.index(indexName);
            var request = indexStore.openCursor(IDBKeyRange.only(condition));
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor) {
                    var student = cursor.value;
                    console.log(student.id);
                    cursor.continue();
                }
            }
        }

        //获取某个store中存储条目的总数
        fetchStoreDataCount(storeName, callback) {
            var count = 0;
            var store = this.getStoreByName(storeName);
            var req = store.count();
            req.onsuccess = function (e) {
                count = e.target.result;
                callback(count);
            }
        }

    }

    //名为students的ObjectStore
    const datas = [{
        id: 11,
        name: "zhangsan",
        age: 24
    }, {
        id: 12,
        name: "lisi",
        age: 30
    }, {
        id: 13,
        name: "wangwu",
        age: 26
    }, {
        id: 14,
        name: "zhaoliu",
        age: 26
    },];

    window.onload = function () {
        var db = new DBmanager('testDB', 4);
        db.openDB();
        setTimeout(function () {
//            db.getDataCountByMultiCondition('students','myIndex',['lisi',30]);
//            db.getDataBetweenTwoData('students','ageIndex',24,30,false,true);
//            db.getMultipleData('students', 'ageIndex', 26);
//            db.fetchStoreByCurser('students');
//            db.getDataByIndex('students','nameIndex','zhangsan');
//            db.addData('students',datas);
//            db.getDataByKey('students',11);
            db.fetchStoreDataCount('students', function (count) {
                console.log(count);
            });
            db.closeDB();
//            db.deleteDB();
        }, 500);
    }
</script>

</body>
</html>

猜你喜欢

转载自blog.csdn.net/qq_23158083/article/details/54707802
今日推荐