Web应用分页功能的简单实现

Web应用的一个简单分页功能,前端直接使用bootstrap的Pagination指令,后端通过nodejs的mongoose驱动skip()和limit()方法来实现分页,也一并把select、sort查询操作总结一些。


  1. Pagination的使用

    bootstrap的这套工具[1]非常好用,直接将标签指令写到指定地方即可。

    <pagination total-items="totalItems" ng-model="currentPage" max-size="maxSize" class="pagination-sm" boundary-links="true" previous-text="上一页" next-text="下一页" first-text="第一页" last-text="最后一页" ng-change="pageChanged()"></pagination>
    

    几个需要赋值的参数有totalItems(条目总数)、currentPage(当前页)、pageLimit(每页数量)和maxSize(页标展示数量)。其中totalItems需要向后端发出查询请求才能赋值。第一次使用时不知道这个组件的文档,直接修改了lib中的默认呈现文字。

  2. 后端与MongoDB的交互

    后端从请求中获得分页条件的参数,然后组合成查询语句。前述的两个具体的功能为:skip(n)方法将指针跳过前n个文档进行,而limit(n)则限定读取n个文档。结合到一起就能实现分页的功能。

    这里也依据下方所写代码的业务将其他的方法也一并讲解。

    //获取url参数
    var getCondition = req.query;
    //邮件查询条件
    var queryCondition = {};
    //数据库返回键值
    var select = 'mail.date mail.subject mail.text mail.html mail.attachments commonStatus score operations privateInfo totalWeight';
    //分页参数
    var curPage = 0;
    var numPerPage = 0;
    var numSkip = 0;
    //排序参数
    var sort = {};
    //经由上述条件的Mails.find方法的Query实例
    var query = {};
    //生成依据状态查询对象
    queryCondition = _generateStatusQuery(getCondition);
    
    
    //判断是否存在排序条件的查询
    if(getCondition.hasOwnProperty('sort')){
      getCondition.sort = JSON.parse(getCondition.sort);
      //强行依据keyword对时效性排序为首要排序位置
      sort['timeBound'] = -1;
      if(getCondition.sort.hasOwnProperty('score')){
        sort['score'] =  getCondition.sort.score;
      } 
      if(getCondition.sort.hasOwnProperty('date')){
        sort['mail.date'] =  getCondition.sort.date;
      }
    }else{
      sort = { 'timeBound': -1, 'mail.date': -1 };
    }
    //判断是否存在分页条件的查询
    if(getCondition.hasOwnProperty('page')){
      getCondition.page = JSON.parse(getCondition.page);
      curPage = getCondition.page.num || 1;
      numPerPage = getCondition.page.limit || 10;
    }else{
      curPage = 1;
      numPerPage = 10;
    }
    //分页 查询结果会跳过前numSkip个邮件
    numSkip = (curPage * numPerPage) - numPerPage;
    numPerPage = 10;
    query = Mails.find(queryCondition).select(select).sort(sort).skip(numSkip).limit(numPerPage).lean();
    

    我们可以使用select()方法选择返回的文档中的哪些键,才用字符串来传递参数,使用空格来分割不同的键;sort()方法则是依据所传的键来对结果排序,采取的格式为对象,赋1为升序,赋-1为降序。

    说到排序,就要考虑索引[2]的使用。在不使用索引的情况下,mongod会使用大量的数据遍历操作,而数据还存储造内存中,这也会超出mongodb的查询内存。而索引能有效避免这个问题。索引在mongodb中只保存小部分的集合数据,并将其排序,这也一定程度加快了查询的速度,因为mongodb默认查询排序是通过自然顺序,这个自然顺序是以mongodb内部数据在数据文件中的组织顺序来排序的。然后mongodb提供两个方法来建立索引:createIndex()、ensureIndex(),这两个函数现在已经没有区别(并且ensureIndex()已经弃用,它在Mongodb的老版本中只会在索引不存在的情况下创建索引),新的mongoose也全部改写为调用createIndex()。想直接使用mongoose就在定义Model相应的键的数据中写上'index: true',比如像图中这样。

    在相对复杂的情况下,需要使用复合索引[3]。复合索引就是创建包含多个集合中键的索引。复合索引支持前缀的组合索引,也就是说在创建{ 'score': 1, 'mail.date': -1, 'hasAttach': 1}的索引中,可以通过查询score或者socre和mail.date字段来使用索引查询,但不支持score和hasAttach键组合的查询。并且索引的俗话逆序也很重要,会安装创建顺序来排序结果。

    在Mongoose中定义Model时,通过Schema.index()方法来创建复合索引。

    //为邮件排序查询创建索引 MailSchema.index({ 'score': -1, 'mail.date': -1 }); MailSchema.index({ 'score': -1, 'mail.date': 1 });

    其实这里的每页条目数numPerPage也应该通过前端传递过来,不过相对这个应用场景简化参数也是没有问题的。

    在大数据量的情况下使用skip+limit会严重影响性能,因为在获取排序的尾部时,mongodb会历遍前面的大量数据量。而比较好的改进方法[4]是在_id上建立索引形成有序的字段来避免大量的依次数数。

    下篇文章介绍mongodb的查询条件操作符。


参考:

  1. bootstrap的pagination

  2. mongodb索引的知识

  3. mongodb的复合索引

  4. cnblogs上的分页测试