MongoDB的使用學習之(七)MongoDB的聚合查詢(兩種方式)附項目源碼


先來張在路上……

鐺鐺鐺……項目源碼下載地址:http://files.cnblogs.com/ontheroad_lee/MongoDBDemo.rar

 

此項目是用Maven創建的,沒有使用Maven的,自己百度、谷歌去;直接用Junit測試就行,先執行里面的save方法,添加10000條測試數據提供各種聚合查詢等。

少廢話,上干貨……

一、MongoDB數據庫的配置(mongodb.xml)

以下是我自己的配置,紅色字體請改為自己本機的東東,你說不懂設置端口,不會創建數據庫名稱,不會配置用戶名密碼,那有請查閱本系列的第4節(MongoDB的使用學習之(四)權限設置--用戶名、密碼、端口==),你說懶得設置,那就@#¥%……&*()!

  <!-- Default bean name is 'mongo' -->
    <!-- 定義mongo對象,對應的是mongodb官方jar包中的Mongo,replica-set設置集群副本的ip地址和端口 -->
    <mongo:mongo id="mongo" host="localhost" port="47017"  />
    
    <mongo:db-factory id="mongoDbFactory" dbname="mongoTest" mongo-ref="mongo" username="root1" password="root1"  />

    <!-- 自動掃描以下包的類 -->
     <!-- 映射轉換器,掃描back-package目錄下的文件,根據注釋,把它們作為mongodb的一個collection的映射 -->
    <mongo:mapping-converter base-package="com.ontheroad.**.po" />

    <bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
    <!-- 配置mongodb映射類型 -->
    <bean id="mappingMongoConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
        <constructor-arg name="mappingContext" ref="mappingContext" />
        <property name="typeMapper" ref="defaultMongoTypeMapper" />
    </bean>

    <!-- 默認Mongodb類型映射 -->
    <bean id="defaultMongoTypeMapper" class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper">
        <constructor-arg name="typeKey">
            <null /><!-- 這里設置為空,可以把 spring data mongodb 多余保存的_class字段去掉 -->
        </constructor-arg>
    </bean>
    <!-- 操作mongodb -->
    <!-- mongodb的主要操作對象mongoTemplate(Spring Data提供),所有對mongodb的增刪改查的操作都是通過它完成 -->
    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
        <constructor-arg name="mongoConverter" ref="mappingMongoConverter" />
    </bean>

 

二、各種聚合查詢方法

以下只是展示一些常用的,聚合查詢方法,無奈個人功力尚淺,沒啥高深的東西,待日后,有時間有精力有實力,再整理些高級一點的

1、添加測試數據

@Test
    public  void save() {
        News n = null;
        for (int i = 0; i < 10000; i++) {
            n = new News();
            n.setTitle("title_" + i);
            n.setUrl("url_" + i);
            
            //2014-01-01到2014-01-01之間的隨機時間
            Date randomDate=DateUtil.randomDate("2014-01-01","2014-05-11");
            //MongoDB里如果時間類型存的是Date,那么會差8個小時的時區,因為MongoDB使用的格林威治時間,中國所處的是+8區,so……
            //比如我保存的是2014-05-01 00:00:00,那么保存到MongoDB里則是2014-05-01 08:00:00,所以為了統一方面,那就保存字符串類型,底下保存的long類型
            n.setPublishTimeStr(DateUtil.formatDateTimeByDate(randomDate));
            //long類型在查詢速度中肯定會比較快
            n.setPublishTime(randomDate.getTime());
            n.setPublishDate(randomDate);
            
            n.setPublishMedia("publishMedia_" + i);
            
            String[] areaArr = {"1024", "102401", "102402", "102403", "102404", "102405", "102406", "102407", "102408"
                    , "10240101", "10240102", "10240201", "10240202", "10240301", "10240302", "10240401", "10240402"
                    , "10240501", "10240502", "10240601", "10240602", "10240701", "10240702", "10240801", "10240802"};
            int areaNum=(int)(Math.random() * areaArr.length);//產生0-strs.length的整數隨機數
            String area = areaArr[areaNum];
            n.setArea(area);
            
            String[] ckeyArr = {"A101", "A102", "A201", "A202", "A203"
                    , "B101", "B102", "B103", "C201", "C202", "C203", "22", "23", "24", "25", "26"};
            int ckeyNum=(int)(Math.random() * ckeyArr.length);//產生0-strs.length的整數隨機數
            List<String> list = new ArrayList<String>();
            for (int j = 0; j < ckeyNum; j ++) {
                int ckeyNum1=(int)(Math.random() * ckeyArr.length);//產生0-strs.length的整數隨機數
                list.add(ckeyArr[ckeyNum1]);
            }
            n.setClassKey(list);
            
            Integer[] evalArr = {1, 0};
            int evalNum=(int)(Math.random() * evalArr.length);//產生0-strs.length的整數隨機數
            n.setEvaluate(evalArr[evalNum]);
            
            Integer[] mproArr = {1, 2, 100};
            int mproNum=(int)(Math.random() * mproArr.length);//產生0-strs.length的整數隨機數
            n.setMediaProperty(mproArr[mproNum]);

            Integer[] mtypeArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
            int mtypeNum=(int)(Math.random() * mtypeArr.length);//產生0-strs.length的整數隨機數
            n.setMediaType(mtypeArr[mtypeNum]);
            
            Integer[] levelArr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
            int levelNum=(int)(Math.random() * levelArr.length);//產生0-strs.length的整數隨機數
            n.setLevel(levelArr[levelNum]);
            
            newsService.save(n);
        }
        System.out.println("OK");
    }

 

2、簡單的分組查詢--使用Mongo本身提供的AggregationOutput進行分組查詢

/**
     * 
     * 功能:使用Mongo本身提供的AggregationOutput進行分組查詢
     * 參數:   
     * 創建人:OnTheRoad_Lee 
     * 修改人:OnTheRoad_Lee 
     * 最后修改時間:2014-5-26
     */
    public void testGroup1 () {
        //按照eval字段進行分組,注意$eval必須是存在mongodb里面的字段,不能寫$evaluate(此字段是News類中定義的,和存入mongodb中的有區別)
        //{$group:{_id:{'AAA':'$BBB'},CCC:{$sum:1}}}固定格式:把要分組的字段放在_id:{}里面,BBB是mongodb里面的某個字段,AAA是BBB的重命名,CCC是$sum:1的重命名
        //此查詢語句== select eval as eval, count(*) as docsNum from news group by eval having docsNum>=85 order by docsNum desc
        //具體的mongodb和sql的對照可以參考:http://docs.mongodb.org/manual/reference/sql-aggregation-comparison/
        String groupStr = "{$group:{_id:{'eval':'$eval'},docsNum:{$sum:1}}}";
        DBObject group = (DBObject) JSON.parse(groupStr);
        String matchStr = "{$match:{docsNum:{$gte:85}}}";
        DBObject match = (DBObject) JSON.parse(matchStr);
        String sortStr = "{$sort:{_id.docsNum:-1}}";
        DBObject sort = (DBObject) JSON.parse(sortStr);
        AggregationOutput output = mongoTemplate.getCollection("news").aggregate(group, match, sort);
        System.out.println(output.getCommand());
        //轉換為執行原生的mongodb查詢語句
        //{ "aggregate" : "news" , "pipeline" : [ { "$group" : { "_id" : { "eval" : "$eval"} , "docsNum" : { "$sum" : 1}}} , { "$match" : { "docsNum" : { "$gte" : 85}}} , { "$sort" : { "_id.docsNum" : -1}}]}
        System.out.println(output.getCommandResult());
        //查詢結果
        //{ "serverUsed" : "localhost/127.0.0.1:47017" , "result" : [ { "_id" : { "evaluate" : 1} , "docsNum" : 9955} , { "_id" : { "evaluate" : 0} , "docsNum" : 10047}] , "ok" : 1.0}
        
        //也可以把查詢結果封裝到NewsNumDTO,這樣以一個dto對象返回前台操作就更容易了
        NewsNumDTO dto = new NewsNumDTO();
        for( Iterator< DBObject > it = output.results().iterator(); it.hasNext(); ){
            BasicDBObject dbo = ( BasicDBObject ) it.next();
            BasicDBObject keyValus = (BasicDBObject)dbo.get("_id");
            int eval = keyValus.getInt("eval");
            long docsNum = ((Integer)dbo.get("docsNum")).longValue();
            if(eval == 1){
                dto.setPositiveNum(docsNum);
            }else {
                dto.setNegativeNum(docsNum);
            }
        }
        
    }

 

3、獲取和testGroup1方法同樣結果的另一種寫法,Spring Data MongoDB隆重登場,語法更加簡潔易懂

    /**
     * 
     * 功能:獲取和testGroup1方法同樣結果的另一種寫法,Spring Data MongoDB隆重登場,語法更加簡潔易懂
     * 參數:   
     * 創建人:OnTheRoad_Lee 
     * 修改人:OnTheRoad_Lee 
     * 最后修改時間:2014-5-26
     */
    public void testAggregation1() {
        TypedAggregation<News> agg = Aggregation.newAggregation(
                News.class,
                project("evaluate")
                ,group("evaluate").count().as("totalNum")
                ,match(Criteria.where("totalNum").gte(85))
                ,sort(Sort.Direction.DESC, "totalNum")
            );

            AggregationResults<BasicDBObject> result = mongoTemplate.aggregate(agg, BasicDBObject.class);
            System.out.println(agg.toString());
            //執行語句差不多
            //{ "aggregate" : "__collection__" , "pipeline" : [ { "$project" : { "evaluate" : 1}} , { "$group" : { "_id" : "$evaluate" , "totalNum" : { "$sum" : 1}}} , { "$match" : { "totalNum" : { "$gte" : 85}}} , { "$sort" : { "totalNum" : -1}}]}
            System.out.println(result.getMappedResults());
            //查詢結果簡潔明了
            //[{ "_id" : 0 , "totalNum" : 10047}, { "_id" : 1 , "totalNum" : 9955}]
            
            //使用此方法,如果封裝好了某一個類,類里面的屬性和結果集的屬性一一對應,那么,Spring是可以直接把結果集給封裝進去的
            //就是AggregationResults<BasicDBObject> result = mongoTemplate.aggregate(agg, BasicDBObject);中的BasicDBObject改為自己封裝的類
            //但是感覺這樣做有點不靈活,其實吧,應該是自己現在火候還不到,還看不到他的靈活性,好處在哪里;等火候旺了再說唄
            //所以,就用這個萬能的BasicDBObject類來封裝返回結果
            List<BasicDBObject> resultList = result.getMappedResults();
            NewsNumDTO dto = new NewsNumDTO();
            for(BasicDBObject dbo : resultList){
                int eval = dbo.getInt("_id");
                long num = dbo.getLong("totalNum");
                if(eval == 1){
                    dto.setPositiveNum(num);
                }else {
                    dto.setNegativeNum(num);
                }
            }
            System.out.println(dto.getPositiveNum());
    }

 

4、previousOperation的簡單使用--為分組的字段(_id)建立別名

/**
     * 
     * 功能:previousOperation的簡單使用--為分組的字段(_id)建立別名
     * 參數:   
     * 創建人:OnTheRoad_Lee 
     * 修改人:OnTheRoad_Lee 
     * 最后修改時間:2014-5-26
     */
    public void testAggregation2() {
        TypedAggregation<News> agg = Aggregation.newAggregation(
                News.class,
//                match(Criteria.where("mediaType").is(100))
                project("evaluate")
                ,group("evaluate").count().as("totalNum")
                ,project("evaluate", "totalNum")
                    .and("eval").previousOperation()
                    //為分組的字段(_id)建立別名
            );
            AggregationResults<BasicDBObject> result = mongoTemplate.aggregate(agg, BasicDBObject.class);
            System.out.println(agg.toString());
//            { "aggregate" : "__collection__" , "pipeline" : [ { "$project" : { "evaluate" : 1}} , { "$group" : { "_id" : "$evaluate" , "totalNum" : { "$sum" : 1}}} , { "$project" : { "evaluate" : "$_id.evaluate" , "totalNum" : 1 , "_id" : 0 , "eval" : "$_id"}}]}
            System.out.println(result.getMappedResults());
//            [{ "totalNum" : 10047 , "eval" : 0}, { "totalNum" : 9955 , "eval" : 1}]
    }

 

5、unwind()的使用,通過Spring Data MongoDB

/**
     * 
     * 功能:unwind()的使用,通過Spring Data MongoDB
     *         unwind()就是$unwind這個命令的轉換,
     *         $unwind - 可以將一個包含數組的文檔切分成多個, 比如你的文檔有 中有個數組字段 A, A中有10個元素, 那么                                 
     *         經過 $unwind處理后會產生10個文檔,這些文檔只有 字段 A不同
     *         詳見:http://my.oschina.net/GivingOnenessDestiny/blog/88006
     * 參數:   
     * 創建人:OnTheRoad_Lee 
     * 修改人:OnTheRoad_Lee 
     * 最后修改時間:2014-5-26
     */
    public void testAggregation3() {
        TypedAggregation<News> agg = Aggregation.newAggregation(
                News.class,
                unwind("classKey")
                ,project("evaluate", "classKey")
//                這里說明一點就是如果group>=2個字段,那么結果集的分組字段就沒有_id了,取而代之的是具體的字段名(和testAggregation()最對比)
                ,group("evaluate", "classKey").count().as("totalNum")
                ,sort(Sort.Direction.DESC, "totalNum")
            );

            AggregationResults<BasicDBObject> result = mongoTemplate.aggregate(agg, BasicDBObject.class);
            System.out.println(agg.toString());
//            { "aggregate" : "__collection__" , "pipeline" : [ { "$unwind" : "$classKey"} , { "$project" : { "evaluate" : 1 , "classKey" : 1}} , { "$group" : { "_id" : { "evaluate" : "$evaluate" , "classKey" : "$classKey"} , "totalNum" : { "$sum" : 1}}} , { "$sort" : { "totalNum" : -1}}]}
            System.out.println(result.getMappedResults());
//            結果就是醬紫,一目了然,怎么操作,就交給你自己了
//            [{ "evaluate" : 0 , "classKey" : "A201" , "totalNum" : 4857}, { "evaluate" : 0 , "classKey" : "23" , "totalNum" : 4857}, { "evaluate" : 0 , "classKey" : "A101" , "totalNum" : 4842}, { "evaluate" : 0 , "classKey" : "24" , "totalNum" : 4806}, { "evaluate" : 1 , "classKey" : "A101" , "totalNum" : 4787}, { "evaluate" : 0 , "classKey" : "C201" , "totalNum" : 4787}, { "evaluate" : 0 , "classKey" : "A102" , "totalNum" : 4783},……]
    }

 

6、Spring Data MongoDB,按照時間(字符串)分組

/**
     * 
     * 功能:Spring Data MongoDB,按照時間(字符串)分組
     * 參數:   
     * 創建人:OnTheRoad_Lee 
     * 修改人:OnTheRoad_Lee 
     * 最后修改時間:2014-5-26
     */
    public void testAggregation4() {
        TypedAggregation<News> agg = Aggregation.newAggregation(
                News.class,
                //project().andExpression()里面是一個表達式
//                詳見api:http://docs.spring.io/spring-data/data-mongodb/docs/current/reference/htmlsingle/#mongo.aggregation
//                搜索  .andExpression 定位到具體的方法模塊
                project("evaluate")
                    .andExpression("substr(publishTimeStr,0,10)").as("publishDate")
                ,group("evaluate", "publishDate").count().as("totalNum")
                ,sort(Sort.Direction.DESC, "totalNum")
            );

            AggregationResults<BasicDBObject> result = mongoTemplate.aggregate(agg, BasicDBObject.class);
            System.out.println(agg.toString());
//            { "aggregate" : "__collection__" , "pipeline" : [ { "$project" : { "evaluate" : 1 , "publishDate" : { "$substr" : [ "$publishTimeStr" , 0 , 10]}}} , { "$group" : { "_id" : { "evaluate" : "$evaluate" , "publishDate" : "$publishDate"} , "totalNum" : { "$sum" : 1}}} , { "$sort" : { "totalNum" : -1}}]}
            System.out.println(result.getMappedResults());
//            [{ "evaluate" : 0 , "publishDate" : "2014-03-09" , "totalNum" : 101}, { "evaluate" : 1 , "publishDate" : "2014-02-14" , "totalNum" : 100}, { "evaluate" : 1 , "publishDate" : "2014-02-11" , "totalNum" : 99}, { "evaluate" : 0 , "publishDate" : "2014-03-17" , "totalNum" : 98}, { "evaluate" : 1 , "publishDate" : "2014-03-26" , "totalNum" : 98},  ……]
//            這個查詢結果貌似不是我們想要的效果,理想,一目了然的效果應該是,以日期為單位,日期底下正面多少,負面多少:
//            [
//            { "publishDate" : "2014-03-09" , "evalInfo" : [{"evaluate" : 0 , "totalNum" : 101}, {"evaluate" : 1 , "totalNum" : 44}]}
//            { "publishDate" : "2014-03-12" , "evalInfo" : [{"evaluate" : 0 , "totalNum" : 11}, {"evaluate" : 1 , "totalNum" : 32}]},
//            ……
//            ]
//            無奈本人功力尚淺,查了N多資料,各種論壇,中文的,英文的都查了,就是找不到Spring Data MongoDB 分組方法
//            ,所以就引出了testAggregation5
    }

 

7、使用原生態mongodb語法,按照時間(字符串)分組,整合查詢結果,使用$push命令

/**
     * 
     * 功能:使用原生態mongodb語法,按照時間(字符串)分組,整合查詢結果,使用$push命令
     * 參數:   
     * 創建人:OnTheRoad_Lee 
     * 修改人:OnTheRoad_Lee 
     * 最后修改時間:2014-5-26
     */
    public void testAggregation5() {
                     
        /* Group操作*/
        String groupStr = "{$group:{_id:{'evaluate':'$eval','ptimes':{$substr:['$ptimes',0,10]}},totalNum:{$sum:1}}}";
        DBObject group = (DBObject) JSON.parse(groupStr);
                     
        /* Reshape Group Result*/
        DBObject projectFields = new BasicDBObject();
        projectFields.put("ptimes", "$_id.ptimes");
        projectFields.put("evalInfo", new BasicDBObject("evaluate","$_id.evaluate").append("totalNum", "$totalNum"));
        DBObject project = new BasicDBObject("$project", projectFields);
                     
        /* 將結果push到一起*/
        DBObject groupAgainFields = new BasicDBObject("_id", "$ptimes");
        groupAgainFields.put("evalInfo", new BasicDBObject("$push", "$evalInfo"));
        DBObject reshapeGroup = new BasicDBObject("$group", groupAgainFields);
         
        /* 查看Group結果 */
        AggregationOutput output = mongoTemplate.getCollection("news").aggregate(group, project, reshapeGroup);
        System.out.println(output.getCommand());
//        { "aggregate" : "news" , "pipeline" : [ { "$group" : { "_id" : { "evaluate" : "$eval" , "ptimes" : { "$substr" : [ "$ptimes" , 0 , 10]}} , "totalNum" : { "$sum" : 1}}} , { "$project" : { "ptimes" : "$_id.ptimes" , "evalInfo" : { "evaluate" : "$_id.evaluate" , "totalNum" : "$totalNum"}}} , { "$group" : { "_id" : "$ptimes" , "evalInfo" : { "$push" : "$evalInfo"}}}]}
        System.out.println(output.getCommandResult());
//        { "serverUsed" : "localhost/127.0.0.1:47017" , "result" : [ 
//          { "_id" : "2014-04-24" , "evalInfo" : [ { "evaluate" : 1 , "totalNum" : 67} , { "evaluate" : 0 , "totalNum" : 76}]} 
//        , { "_id" : "2014-02-05" , "evalInfo" : [ { "evaluate" : 1 , "totalNum" : 70} , { "evaluate" : 0 , "totalNum" : 84}]}
//        , { "_id" : "2014-03-21" , "evalInfo" : [ { "evaluate" : 0 , "totalNum" : 82} , { "evaluate" : 1 , "totalNum" : 89}]}}……] 
//        , "ok" : 1.0}
    }

 


免責聲明!

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



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