除了特殊注釋外,本文的測試結果均基於 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語句