轉載請注明: 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把最上級也就是主題的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回復一樣。
暫時更新這么多,以后如果有新的發現,會不定期更新此文。