zoukankan      html  css  js  c++  java
  • mongoose多级嵌套操作

    转载请注明: TheViper http://www.cnblogs.com/TheViper 

    模型就以贴吧为例

    var themeModel = modelBase._mongoose.model('theme',modelBase._schema({
        theme_name:String,
        posts:[{
            post_id:String,
            title:String,
            content:String,
            time:Number,
            user:{type:objectId,ref:'user'},
            img_list:[String],
            suppost:Boolean,
            post_ref:String,
            comments:[{
                content:String,
                time:Number,
                from:{type:objectId,ref:'user'},
                to:{type:objectId,ref:'user'}
            }]
        }]
    }),'theme');

    这里模型故意弄的复杂点,其实就是多点嵌套。实际开发时请根据不同场景,性能,扩展等因素综合考虑。

    mongodb其实查询性能是比不上常见的关系数据库的,只是写的性能要好些,另外,no sheme模型让模型可以很灵活,就像上面那样,可以一个collection就包含关系数据库的几个表.所以这里就着重用它的优点来看看。

    下面解释下上面的model:

    最上面是主题theme,比如什么吧。然后是主题,一个theme有很多主题。接着是回复,也是一个主题有多个回复。最后是评论,又是一个回复有多个评论。

    具体到上面的代码,我为了偷懒,就把主题和回复和并在一起了,这就不严谨了,因为百度贴吧的回复是没有title的,而主题是必须有的,这里就不管这么多了,反正只是个演示。

    注意到有个字段是post_ref,如果post是主题,那post_ref就和post_id是一样的值;如果是回复,那post_ref就是它的上级,也就是主题的post_id.suppost是true,表示是主题;false表示是主题的回复。另外还有个表是user(id,name)就不列了。

    我胡乱弄了点数据

    1.查询主题

            return function(fn){
                themeModel.aggregate().unwind('posts').match({'posts.post_ref':post_id,'posts.user':suppost_user})
                .sort({"posts.time":1}).skip(skip).limit(limit).group({_id:"$_id",posts:{$push:"$posts"}}).exec()
                .then(function(theme){
                    return themeModel.populate(theme,[{path:'posts.user',select:'name'},{path:'posts.comments.from',select:'name'},
                        {path:'posts.comments.to',select:'name'}]);
                }).then(function(theme){
                    fn(null,theme);
                });
            };

    可以看到这里用了mongodb的aggregation。关于aggregation,这里有一个sql to aggregation mapping chart

    $unwind

    可以看到unwind把最上级也就是主题的theme_name,_id分别添加到posts里面的每一个项.这个就是解决mongodb多级嵌套的神器,后面可以看到每个场景都用到了这个unwind.另外aggregate是严格按照顺序执行,也就是unwind()在这里必须在最前面,否则没有效果。

    执行完unwind后就会发现后面的match({'posts.suppost':suppost}),sort({"posts.time":-1})就很简单了,因为现在不是数组,而是对象了,直接用.运算符取就可以取到嵌套的字段了。

    $group可以想成sql里面的group by,但是有些不一样,$group实际上是让你自定义聚合后需要返回哪些字段,因此$group里面至少要有_id,哪怕是group({_id:null})也可以。

    后面的posts:{$push:"$posts"}是将posts每一项重新添加到新的posts数组,这里一定要写出"$posts".

    然后exec()返回Promise,再在then里面对字段里面与user集合有关联的字段进行填充(populate),取出user集合里面的name.

    这里是瀑布流

    2.查询回复

        this.getPostByPostId=function(post_id,skip,limit){
            return function(fn){
                themeModel.aggregate().unwind('posts').match({'posts.post_ref':post_id})
                .sort({"posts.time":1}).skip(skip).limit(limit).group({_id:"$_id",posts:{$push:"$posts"}}).exec()
                .then(function(theme){
                    return themeModel.populate(theme,[{path:'posts.user',select:'name'},{path:'posts.comments.from',select:'name'},
                        {path:'posts.comments.to',select:'name'}]);
                }).then(function(theme){
                    fn(null,theme);
                });
            };
        };

    可以看到和第一个相比,查询条件变成了'posts.post_id':post_id,其他的都差不多

    3.获取主题总数

        this.getSuppostCount=function(theme_name){
            return function(fn){
                themeModel.aggregate().unwind('posts').match({theme_name:theme_name,'posts.suppost':true})
                .group({_id:null,count:{$sum:1}}).exec()
                .then(function(theme){
                    fn(null,theme);
                });
            };
        };

    4.获取回复总数

        this.getPostCount=function(theme_name,post_id){
            return function(fn){
                themeModel.aggregate().unwind('posts').match({'posts.post_ref':post_id})
                .group({_id:"$_id",count:{$sum:1}}).exec()
                .then(function(post){
                    fn(null,post);
                });
            };
        };

    5.插入评论

        this.insertComment=function(from,to,content,time,theme_name,post_id){
            return themeModel.update({theme_name:theme_name,'posts.post_id':post_id},{$push:{'posts.$.comments':{
                from:from,
                to:to,
                content:content,
                time:time
            }}});
        };

    由于是多级嵌套,所以这里实际是update.注意,这里$push的对象是posts.$.comments,而不是posts.comments.$在这里是个占位符,官方文档说的很详细。

    另外,$是个占位符意外着update的条件必须要能够确定posts.$  ,所以在这里条件中必须要有'posts.post_id':post_id。

    6.我发出的主题

        this.getSuppostByUser=function(id){
            return function(fn){
                themeModel.aggregate().unwind('posts').match({'posts.user':mongoose.Types.ObjectId(id),'posts.suppost':true})
                .group({_id:"$_id",posts:{$push:"$posts"}}).exec()
                .then(function(post){
                    console.log(post)
                    fn(null,post);
                });
            };
        };

    user是外键,实际上存储的是用户的id,这里一定要写成mongoose.Types.ObjectId(user),不能直接是user,另外也不要写成建立schema时用的mongoose.Schema.Types.ObjectId,这个很容易搞混。

    7.回复我的

    和6差不多,条件改成'posts.suppost':false就可以了。

    8.回复我的评论

    查询条件是"posts.comments.to":user,而posts.comments是数组,所以很自然的想到用$elemMatch匹配。

        this.reply_me=function(user){
            return function(fn){
                themeModel.aggregate()
                .unwind('posts')
                .match({'posts.comments':{$elemMatch:{to:modelBase._mongoose.Types.ObjectId(user)}}})
                .sort({"posts.comments.time":-1})
                .exec().then(function(theme){
                    fn(null,theme);
                });
            };
        };

    可以看到多出了两个结果,正确的结果应该是to为后四位是2534的comment.

    造成这种的结果的原因在于坑爹的$elemMatch.数组中有一个结果满足条件,mongodb就会将这个数组里的元素一并取出。

    解决方法就是用两个unwind.

        this.reply_me=function(user){
            console.log('user:'+user)
            return function(fn){
                themeModel.aggregate()
                .project('posts.comments theme_name posts.title posts.post_id')
                .unwind('posts')
                .unwind('posts.comments')
                .match({'posts.comments.to':modelBase._mongoose.Types.ObjectId(user)})
                .sort({"posts.comments.time":-1})
                .exec().then(function(theme){
                    fn(null,theme);
                });
            };
        };

    具体的comments结果

    9.修改最后更新时间

    在post内的主题添加字段update_time,每次有新的主题回复,就更新这个主题的update_time。

        this.update_time=function(theme_name,post_id,time){
            return themeModel.update({theme_name:theme_name,'posts.post_id':post_id},{$set:{'posts.$.update_time':time}});
        };

    这里也用到了$,原理和上面查询comment回复一样。

    暂时更新这么多,以后如果有新的发现,会不定期更新此文。

     

  • 相关阅读:
    cookie和session学习笔记
    Listener和Filter学习笔记
    [转载]mysql root密码忘了怎么办
    [转载]oracle连不上的各种现象
    Oauth入门学习
    XML学习笔录
    共享内存
    守护进程
    Linux系统调用与文件I/O(一)
    我的第一篇博客
  • 原文地址:https://www.cnblogs.com/TheViper/p/4317660.html
Copyright © 2011-2022 走看看