本地数据库IndexedDB - 学员管理系统之条件筛选(四)

        IndexedDB是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB允许存储大量数据,提供查找接口,还能建立索引。这些都是LocalStorage或Cookie不具备的。就数据库类型而言,IndexedDB不属于关系型数据库(不支持SQL查询语句),更接近NoSQL数据库。

        这篇将讲解下关于IndexedDB的条件筛选,通过内置功能函数,相对增加查询速度。在之前一篇中,是通过在utils.js工具包中定义分页函数,来实现分页的,这里我们将通过IndexedDB提供的相关函数来实现分页功能。

        效果如下图:

本地数据库IndexedDB - 学员管理系统之条件筛选(四)_第1张图片

一、通过utils工具类实现分页

1.1 getAll()

        IDBObjectStore接口的getAll()方法返回一个IDBRequest对象,该对象包含对象存储中与指定参数匹配的所有对象,如果没有给出参数,则返回存储中的所有对象。

        如果成功找到一个值,则创建该值的结构化克隆,并将其设置为请求对象的结果。

        此方法产生相同的结果:

  • 数据库中不存在的记录
  • 具有未定义值的记录

        如果要分别获取,可以使用以下方法:    

  1. 使用相同键的openCursor()方法。如果记录存在,该方法提供一个游标,如果记录不存在,则不提供游标。
  2. 具有相同键的count()方法,如果行存在,该方法将返回1,如果行不存在,则返回0。

语法:

getAll()
getAll(query)
getAll(query, count)

参数:

名称 描述
query 要查询的键或IDBKeyRange。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
count 指定如果找到多个值要返回的值的数量。如果它小于0或大于2^32 - 1,则会抛出TypeError异常。

 返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。
 

1.2 获取全部数据

        这里是通过getAll()函数,一次拿到所有数据,再通过utils工具类中genderPage()函数对数据进行筛选分页。

        原方utils/utils.js工具包中方法:

/**
 * 生成分页数据
 */
export const genderPage = (data, param) => {
  //判断分页数据是否存在,否则赋值默认参数
  param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
		page: 1,
		pageSize: 10
	}
	let newData = data.map(item => item),
			start = (param.page - 1) * param.pageSize,
			end = newData.length - start < param.pageSize ? newData.length : start + param.pageSize;
	return newData.slice(start, end);
}

        原数据库操作类db/model/student.js分页查询函数代码如下:

/**
 * 获取 学员列表 - 分页模式
 * @param name 查询关键词
 * @param param 分页参数
 */
export const loadStudentPage = (name, param) => {
  return new Promise((resolve, reject) => {
    //打开游标
    let {store} = openTransactionIndex(storeName);
    //获取所有数据
    let alls = store.getAll();

    alls.onerror = function(e){
      reject(
        rJson(0, e, '查询出错了~')
      );
    }

    alls.onsuccess = function(e){
      let result = e.target.result;
      if(result){
        let rData = result;
        if(name){
          rData = result.filter(item => item.name.includes(name));
        }

        getClassByIdsCursor(rData.filter(item => item.cid).map(item => item.cid)).then(res => {
          rData = rData.map(item => {
            if('undefined'!==typeof res[item.cid]){
              item['classify'] = res[item.cid]['name'];
            }
            return item;
          })

          //通过genderPageData函数进行分页处理
          resolve(
            rJson(1, {
              list: genderPage(rData, param),
              total: rData.length
            }, '获取成功~')
          )
        }).catch(e => {
          reject(
            rJson(0, e, '查询出错了~')
          )
        });

      }else{
        reject(
          rJson(0, null, '未查询到数据~')
        )
      }
      // console.log('store', result);
    }

  });
}

二、IndexedDB内置函数实现分页

2.1 getAll()

        以上方法每次切换分页,都需要一次获取所有表中数据,获取到数据后,再进行分页处理。这种方法在数据量不大情况下可以使用,一旦数据量过大则查询非常慢,这里getAll()函数已提供了相应参数,具体入参前面已列出。

        我们通过以下两步即可改造好分页功能,具体如下:

第一步:打开db/model/student.js,修改loadStudentPage()函数,使用getAllKeys()获取所有索引,通过分页参数计算出分页起始位置ID,再通过IDBKeyRange.lowerBound指定查询起始位置,代码如下:

/**
 * 获取 学员列表 - 分页模式
 * @param param 参数
 * @description 通过IndexedDB内置函数实现分页
 */
export const loadStudent2Page = param => {
  //判断分页数据是否存在,否则赋值默认参数
  param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
  	page: 1,
  	pageSize: 10
  };

  return new Promise((resolve, reject) => {
    //打开游标
    let {store} = openTransactionIndex(storeName);
    //获取所有keys
    let keys = store.getAllKeys();
    //获取当前表数量总条数
    let countResult = store.count();

    keys.onerror = function(e){
      reject(
        rJson(0, e, '查询出错了~')
      );
    }

    keys.onsuccess = function(e){
      let allKeys = e.target.result;
      //获取当前分页开始位置ID
      let start = (param.page - 1) * param.pageSize;
      //获取分页起始位
      let lowerKey = IDBKeyRange.lowerBound(allKeys[start]);
      //获取分页数据
      let alls = store.getAll(lowerKey, param.pageSize);

      alls.onerror = function(e){
        reject(
          rJson(0, e, '查询出错了~')
        );
      }

      alls.onsuccess = function(e){
        let result = e.target.result;
        if(result){
          //获取班级信息
          getClassByIdsCursor(result.filter(item => item.cid).map(item => item.cid)).then(res => {
            result = result.map(item => {
              if('undefined'!==typeof res[item.cid]){
                item['classify'] = res[item.cid]['name'];
              }
              return item;
            })

            //通过genderPageData函数进行分页处理
            resolve(
              rJson(1, {
                list: result,
                total: countResult.result,
              }, '获取成功~')
            )
          }).catch(e => {
            reject(
              rJson(0, e, '查询出错了~')
            )
          });

        }else{
          reject(
            rJson(0, null, '未查询到数据~')
          )
        }

      }
    }
    //keys end

  });
}

第三步:打开/db/api/index.js,添加loadStudent2Page()请求,代码如下:

import {
  loadStudent2Page
} from '@/db/model/student'

/**
 * 获取分页数据,不带筛选
 */
export const getStudentPage2List = (param) => {
  return loadStudent2Page(param);
}

第三步:打开src/pages/student/index.vue,修改updateList()函数;引入接口getStudentPage2List()函数,列表页代码如下:

updateList(){
	getStudentPage2List({
		page: this.page,
		pageSize: this.pageSize
	}).then(res => {
		if(res.code==1){
			this.pageTotal = res.data['total'];
			this.tableList = res.data['list'].map(item => {
				item['createtime'] = formatDate(item.createtime);
				item['updatetime'] = formatDate(item.updatetime);
				if(item['avatar']){
					item.avatar = URL.createObjectURL(item.avatar);
				}
				return item;
			});
		}
	}).catch(e => {
		console.error(e);
	})
}

        实现后效果如下:

本地数据库IndexedDB - 学员管理系统之条件筛选(四)_第2张图片

         此时,切换分页效果和使用utils工具包中genderPage()函数是一样的,而且是在指定范围内查询,遇到数据量大的情况下,速度得到大大的提升。

2.2 倒序排列

        想必在前面章节中,大家就应该发现数据是正序排列的,最新数据一直排在后面,我们可以在“二、getAll实现分页”的基本上,稍微调整下即可实现倒叙排列。

        原代码如下:

let allKeys = e.target.result;
//获取当前分页开始位置ID
let start = (param.page - 1) * param.pageSize;
//获取分页起始位
let lowerKey = IDBKeyRange.lowerBound(allKeys[start]);

        将以上代码,先把allKeys作个反转,通过分页参数计算出当前分页区间,再将获取的开始和结束位置的索引,赋值到IDBKeyRange.bound中即可,修改后代码如下:

 let allKeys = e.target.result.reverse();
//获取当前分页开始位置ID
let end = (param.page - 1) * param.pageSize,
    start = (allKeys.length - end < param.pageSize ? allKeys.length : end + param.pageSize) - 1;

//获取分页起始位
let lowerKey = IDBKeyRange.bound(allKeys[start], allKeys[end]);

        此时会发现列表中显示数据,已经是倒叙查询出来的,但是数据还是以正序结果在显示。正常情况应该是“刘博”排在第一位,但是现在是”陈可“,如下图:

本地数据库IndexedDB - 学员管理系统之条件筛选(四)_第3张图片

         其实解决这个问题很简单,还是将最终结果反转一下即可,result后面添加reverse()函数,代码如下:

resolve(
    rJson(1, {
        list: result.reverse(),
        total: countResult.result,
    }, '获取成功~')
)

        此时显示则为倒序排列了,如下图:

本地数据库IndexedDB - 学员管理系统之条件筛选(四)_第4张图片

 2.3 关键词搜索

        IDBObjectStore 接口的 openCursor ()方法返回一个 IDBRequest 对象,并在一个单独的线程中返回一个新的 IDBCursorWithValue 对象。用于使用游标遍历对象存储区。

        语法:

openCursor()
openCursor(query)
openCursor(query, direction)

        参数:

名称 描述
query 要查询的键或IDBKeyRange。如果传递了一个有效键,则默认为只包含该键的范围。如果不传递任何信息,则默认为选择此对象存储中的所有记录的键范围。
direction

指示光标移动方向的字符串。默认值是next。有效值:

next:游标在存储的开始处打开;然后,光标按键的递增顺序返回所有记录,甚至是重复的记录。

nextunique:游标在存储的开始处打开;然后,游标按键的递增顺序返回所有非重复的记录。

prev:游标在存储的开始处打开;然后,光标按键的递减顺序返回所有记录,甚至是重复的记录。

prevunique:游标在存储的开始处打开;然后,游标按键的递减顺序返回所有不重复的记录。

返回值:

        一个IDBRequest对象,在该对象上触发与此操作相关的后续事件。

        写到这里有些人可能还未发现,改造完后的列表中关键词条件筛选已失效了。细心朋友应该已经发现getAll()无法满足模糊条件查询,此时我们改用openCursor()游标来实现 模糊分页查询功能。使用openCursor()方便之处在于倒序查询直接可以通过它来实现了,上面已列出了入参值,在第二个参数位置填入”prev”即可。

        列表页pages/student/index.vue中updateList()函数中添加name关键词,代码如下:

/**
 * 查询数据
 */
searchEvent(){
	this.page = 1;
	this.updateList();
},
/**
 * 获取列表数据
 */
updateList(){
	getStudentPage2List({
		page: this.page,
		pageSize: this.pageSize,
		name: this.keyword
	}).then(res => {
		if(res.code==1){
			this.pageTotal = res.data['total'];
			this.tableList = res.data['list'].map(item => {
				item['createtime'] = formatDate(item.createtime);
				item['updatetime'] = formatDate(item.updatetime);
				if(item['avatar']){
					item.avatar = URL.createObjectURL(item.avatar);
				}
				return item;
			});
		}
	}).catch(e => {
		console.error(e);
	})
},

         打开db/model/student.js,修改loadStudentPage()函数,代码如下:

/**
 * 获取 学员列表 - 分页模式
 * @param param 参数
 * @description 通过IndexedDB内置函数实现分页,倒序查询,以及关键词模糊查询
 */
export const loadStudent4Page = param => {
  //判断分页数据是否存在,否则赋值默认参数
  param = param && 'undefined'!==typeof param['page'] && 'undefined'!==typeof param['pageSize'] ? param : {
  	page: 1,
  	pageSize: 10,
    name: ""
  };

  return new Promise((resolve, reject) => {
    //打开游标
    let {store} = openTransactionIndex(storeName);
    //返回结果集
    let rData = [];
    //结果区间
    let start =  (param.page - 1) * param.pageSize,
        end = start + param.pageSize,
        index = 0;

    store.openCursor(null, 'prev').onsuccess = function(e){
      let cursor = e.target.result;
      if(cursor){
        //判断关键词是否被包含
       if(cursor.value.name.includes(param.name)){
          if(index>=start&&index item.cid).map(item => item.cid)).then(res => {
          rData = rData.map(item => {
            if('undefined'!==typeof res[item.cid]){
              item['classify'] = res[item.cid]['name'];
            }
            return item;
          })

          //通过genderPageData函数进行分页处理
          resolve(
            rJson(1, {
              list: rData,
              total: index,
            }, '获取成功~')
          )
        }).catch(e => {
          reject(
            rJson(0, e, '查询出错了~')
          )
        });

      }

    }

  });
}

 此时我们在搜索框中输入“刘”,则可以模糊查询出结果了,如下图:

本地数据库IndexedDB - 学员管理系统之条件筛选(四)_第5张图片

本地数据库IndexedDB - 学员管理系统之条件筛选(四)_第6张图片

 总结:

        不管是通过getAll()一次性获取所有数据,还是通过IDBKeyRange实现条件查询,或是通过openCursor()游标查询,实现过程中都遇到短板。其实仔细分析新增的loadStudent4Page()函数,虽然使用openCursor()游标查询,已通过start和end将分页数据查询完成,但还需要将所有数据遍历一次,来获取当前模糊查询结果的总条数。相比getAll()函数一次获取再通过utils工具包中genderPage()实现分页,性能是相差不大的。

        由于IndexedDB是非关系性数据库,使用SQL习惯的程序员对此不太适应,用起来比较别扭,可根据实际需求,使用适合自己的方法来实现相应的业务。

你可能感兴趣的:(IndexedDB,数据库,前端,javascript,vue.js)