spring-data-mongodb 使用原生aggregate語句


除了特殊注釋外,本文的測試結果均基於 spring-data-mongodb:1.10.6.RELEASE(spring-boot-starter:1.5.6.RELEASE),MongoDB 3.0.6


   考慮到大多數人都是來找答案的,所以先給出結論

// import org.springframework.data.mongodb.core.MongoTemplate;
mongoTemplate.getDb().doEval("db.user.aggregate([{$group:{_id:'$name',count:{$sum:'$age'}}}])");

注意:

  1、mongo shell 使用js語法,可以使用單引號或者雙引號表示字符串,這里使用單引號,可以避免大量的 \ 轉義符

  2、原生語句中的key:value部分,value只能是 [xxx] 、{xxx} 、 ‘xxx’ 三種格式的數據。

  2.1.2版本的spring-data-mongodb已經去掉了doEval方法,我們可以使用下面的方法自己拼接

//spring-boot-starter:2.1.0.RELEASE spring-data-mongodb:2.1.2.RELEASE
BasicDBObject bson = new BasicDBObject();
bson.put("$eval","db.user.aggregate([{$group:{_id:'$name',count:{$sum:'$age'}}}])");
Object object = mongoTemplate.getDb().runCommand(bson);

  其他的解決方法,如調用存儲過程、拼接完整的BasicDBObject、繼承Aggregate請見第三章:SPRING-DATA-MONGODB底層與MONGO-DRIVER的交互


 

  研究這個是因為遇到了一個業務需求,需要使用多種限制條件,返回多個統計字段。spring-data-mongodb提供的API不足以實現這么復雜的業務,所以就想到了直接使用原生的aggregate查詢。

  mongo底層實現查詢的方法主要有兩種,一種是 db._collection_.doSomething({ ... }) ,另一種是db.runCommand({doSomething:_collection_ , ... }) ,我們將第一種稱作函數,第二種稱作命令

  那么,哪種才是 aggregate的原生查詢?spring-data-mongodb底層究竟調用的是函數還是命令?如果你只是想完成工作的話,copy上面的代碼,然后右上角點×,如果你想解決問題學點東西的話,歡迎繼續看下去,我這邊寫了四章詳細的分析過程,鏈接在底部

======2019-11-15補充原生語句轉換方式補充======================================================

  提供了一個執行原生字符串aggregate語句的方法,對外暴露方法 dbAggregate(collectionName, pipeline)

  1 import com.mongodb.BasicDBList;
  2 import com.mongodb.BasicDBObject;
  3 import org.apache.commons.lang.StringUtils;
  4 import org.springframework.beans.factory.annotation.Autowired;
  5 import org.springframework.data.mongodb.core.MongoTemplate;
  6 import org.springframework.stereotype.Component;
  7 
  8 import javax.annotation.PostConstruct;
  9 import java.util.LinkedList;
 10 
 11 @Component
 12 public class MongoUtil {
 13 
 14     @Autowired
 15     MongoTemplate mongoTemplate;
 16 
 17     @PostConstruct
 18     public void test() {
 19         dbAggregate("user",
 20                 "([{$match:{name:wwl}},{$group:{_id:$name,count:{$sum:#NUM1}}},{$project:{count:#BOOtrue}}])");
 21     }
 22 
 23     /**
 24      * aggregate原生語句執行方法
 25      * @param collectionName 表名
 26      * @param pipeline 管道操作語句。【:后面的內容,默認string,數字或者布爾加標識符#NUM,#BOO】
 27      * @return {“result”:[結果], "ok":查詢狀態}
 28      */
 29     public Object dbAggregate(String collectionName, String pipeline){
 30         BasicDBObject bdr = new BasicDBObject();
 31         bdr.put("aggregate", collectionName);
 32         char[] c = pipeline.toCharArray();
 33         bdr.put("pipeline", appendWithChar(c));
 34         return mongoTemplate.getCollection("$cmd").findOne(bdr);
 35     }
 36 
 37 
 38     /**
 39      * 根據標點分組,並對分組數據處理轉出最終的參數
 40      * @param c 待分組的字符串數組
 41      * @return 轉換
 42      */
 43     private static Object appendWithChar(char[] c){
 44         LinkedList<Object> valuelist = new LinkedList();
 45         StringBuffer sb = new StringBuffer();
 46         for(char ele : c){
 47             if('{' == ele){
 48                 valuelist.add(new BasicDBObject());
 49             }else if('[' == ele){
 50                 valuelist.add(new BasicDBList());
 51             }else if(',' == ele || ']' == ele || '}' == ele){
 52                 if(sb.length() > 0){
 53                     valuelist.add(sb.toString());
 54                     sb.delete(0 , sb.length());
 55                 }
 56                 insertValue(valuelist);
 57             }else if(':' == ele){
 58                 valuelist.add(sb.toString());
 59                 sb.delete(0 , sb.length());
 60             }else{
 61                 sb.append(ele);
 62             }
 63         }
 64         return valuelist.getLast();
 65     }
 66 
 67     /**
 68      * 根據數據類型插入數據
 69      * @param valuelist [obj1,obj2] obj1.add(obj2) 或者 obj.put(obj1)。
 70      *                  add完后obj2失效,obj1有機會進入下一次插入數據判斷;put完后obj1和obj2都失效
 71      */
 72     private static void insertValue(LinkedList<Object> valuelist) {
 73         Object value1 = checkValue(valuelist.removeLast());
 74         Object value2 = valuelist.getLast();
 75         if( value2  instanceof BasicDBList ){
 76             ((BasicDBList)value2).add(value1);
 77         }else{
 78             valuelist.removeLast();
 79             BasicDBObject dbObject = (BasicDBObject)valuelist.getLast();
 80             dbObject.put(value2.toString(), value1);
 81         }
 82     }
 83 
 84     /**
 85      * 根據標識符 #NUM\#BOO 判斷value是否需要強轉
 86      * @param o 待判斷是否需要強轉的參數
 87      * @return  處理后的參數
 88      */
 89     private static Object checkValue(Object o) {
 90         try {
 91             String[] str = StringUtils.split(o.toString(), "NUM");
 92             if( 2 == str.length &&  StringUtils.equals("#",str[0]) ){
 93                 return Long.parseLong(str[1]);
 94             }
 95 
 96             String[] str2 = StringUtils.split(o.toString(), "BOO");
 97             if( 2 == str2.length &&  StringUtils.equals("#",str2[0]) ){
 98                 return Boolean.valueOf(str2[1]);
 99             }
100         }catch (Exception e){
101             System.out.println("強轉失敗");
102             e.printStackTrace();
103         }
104         return o;
105     }
106 }

 


 


 目錄

  一:spring-data-mongodb 使用原生aggregate語句

  二:mongo的runCommand與集合操作函數的關系

  三:spring-data-mongodb與mongo shell的對應關系

  四:mongo中的游標與數據一致性的取舍


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM