Spring Data MongoDB 教程

分组操作

在 MongoDB 中,我们可以将分组操作用作 Map-Reduce 进行数据聚合的替代方法。分组操作类似于使用 SQL 语句的按查询方式分组,所以与使用 Map-Reduce 相比,分组操作可能感觉更容上手。

注意:使用分组操作有一些限制,例如:在共享环境中不支持,而且它在一个 BSON 对象中返回全部结果集,所以结果应该要很小,少于 10000 个键。

Spring Data MongoDB 通过在 MongoOperations 上提供方法来简化组操作的创建和运行,从而提供了与 MongoDB 的组操作的集成。它可以将组操作的结果转换为 POJO,还可以与 Spring 的资源抽象集成。这将让你把你的 JavaScript 文件放在文件系统、classpath、http 服务器或任何其他 Spring 资源实现上,然后通过简单的 URI 风格的语法引用 JavaScript 资源,例如:classpath:reduce.js。

提示:在文件中外部化 JavaScript 代码通常比在代码中将 JavaScript 作为 Java 字符串嵌入要好,也推荐这样做。当然,你还是可以将 JavaScript 代码作为 Java 字符串嵌入,只是不推荐这样做而已。

示例

为了理解组操作是如何工作的,我们使用了下面的例子。创建了一个名为 group_test_collection 的集合,集合有以下几条记录:

{ "_id" : ObjectId("4ec1d25d41421e2015da64f1"), "x" : 1 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f2"), "x" : 1 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f3"), "x" : 2 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f4"), "x" : 3 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f5"), "x" : 3 }
{ "_id" : ObjectId("4ec1d25d41421e2015da64f6"), "x" : 3 }

我们想按每行中唯一的字段(即 x 字段)进行分组,并汇总 x 的每个特定值出现的次数。要做到这一点,我们需要创建一个初始文件,其中包含我们的 count 变量和一个 reduce 函数,该函数将在每次遇到它时增加它。运行分组操作的 Java 代码如下所示:

GroupByResults<XObject> results = mongoTemplate.group("group_test_collection",
       GroupBy.key("x").initialDocument("{ count: 0 }")
           .reduceFunction("function(doc, prev) { prev.count += 1 }"),
       XObject.class);

代码说明:

  • 第一个参数是要运行分组操作的集合的名称,这里是:group_test_collection

  • 第二个参数是一个流式 API,通过 GroupBy 类指定分组操作的属性。在这个例子中,我们只使用 intialDocument() 和 reduceFunction() 方法。你也可以指定一个 key-function,以及一个 finalizer 作为 流式 API 的一部分。如果你有多个键要分组,你可以传入一个逗号分隔的键列表。

MongoDB 组操作的原始结果是一个 JSON 文档,看起来如下:

{
 "retval" : [ { "x" : 1.0 , "count" : 2.0} ,
              { "x" : 2.0 , "count" : 1.0} ,
              { "x" : 3.0 , "count" : 3.0} ] ,
 "count" : 6.0 ,
 "keys" : 3 ,
 "ok" : 1.0
}

注意,retval 字段下的文件将被映射到组方法的第三个参数上,这个例子是 XObject,XObject 的代码如下:

public class XObject {
 private float x;
 private float count;

 public float getX() {
   return x;
 }

 public void setX(float x) {
   this.x = x;
 }

 public float getCount() {
   return count;
 }

 public void setCount(float count) {
   this.count = count;
 }

 @Override
 public String toString() {
   return "XObject [x=" + x + " count = " + count + "]";
 }
}

你也可以通过调用 GroupByResults 类上的 getRawResults() 方法来获得原始结果作为一个文档。

MongoOperations 上的 group() 方法有一个额外的方法重载,可以让你指定一个 Criteria 对象来选择一个行的子集。下面是一个使用 Criteria 对象的例子,其中使用了一些静态导入的语法,并通过 Spring 资源字符串引用了一个 keyFunction.js 和 groupReduce.js 文件。如下:

import static org.springframework.data.mongodb.core.mapreduce.GroupBy.keyFunction;
import static org.springframework.data.mongodb.core.query.Criteria.where;

GroupByResults<XObject> results = mongoTemplate.group(where("x").gt(0),
       "group_test_collection",
       keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }")
           .reduceFunction("classpath:groupReduce.js"), XObject.class);

完整示例代码

关于怎样配置 MongoTemplate 这里不在赘述,下面直接给出完整代码。

(1)keyFunction.js 代码

function keyFunction(prev) {
   return { "x": prev.x };
}

(2)groupReduce.js 代码

function groupReduce(doc, prev) {
   prev.count += 1
}

(3)XObject.java 代码

package com.hxstrive.springdata.mongodb.entity;

public class XObject {
   private float x;
   private float count;

   public float getX() {
       return x;
   }

   public void setX(float x) {
       this.x = x;
   }

   public float getCount() {
       return count;
   }

   public void setCount(float count) {
       this.count = count;
   }

   @Override
   public String toString() {
       return "XObject [x=" + x + " count = " + count + "]";
   }
}

(4)GroupDemo.java 代码

package com.hxstrive.springdata.mongodb;

import com.hxstrive.springdata.mongodb.entity.XObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapreduce.GroupBy;
import org.springframework.data.mongodb.core.mapreduce.GroupByResults;
import java.util.function.Consumer;
import static org.springframework.data.mongodb.core.mapreduce.GroupBy.keyFunction;
import static org.springframework.data.mongodb.core.query.Criteria.where;

/**
* 分组操作
* @author hxstrive.com
*/
@SpringBootTest
class GroupDemo {

   @Autowired
   private MongoTemplate mongoTemplate;

   @BeforeEach
   public void init() {
       mongoTemplate.dropCollection("group_test_collection");

       // 准备数据
       String[] datas = {
               "{ \"_id\" : ObjectId(\"4ec1d25d41421e2015da64f1\"), \"x\" : 1 }",
               "{ \"_id\" : ObjectId(\"4ec1d25d41421e2015da64f2\"), \"x\" : 1 }",
               "{ \"_id\" : ObjectId(\"4ec1d25d41421e2015da64f3\"), \"x\" : 2 }",
               "{ \"_id\" : ObjectId(\"4ec1d25d41421e2015da64f4\"), \"x\" : 3 }",
               "{ \"_id\" : ObjectId(\"4ec1d25d41421e2015da64f5\"), \"x\" : 3 }",
               "{ \"_id\" : ObjectId(\"4ec1d25d41421e2015da64f6\"), \"x\" : 3 }"
       };
       for(String data : datas) {
           mongoTemplate.save(data, "group_test_collection");
       }
   }


   @Test
   public void group1() {
       GroupByResults<XObject> results = mongoTemplate.group("group_test_collection",
               GroupBy.key("x").initialDocument("{ count: 0 }")
                       .reduceFunction("function(doc, prev) { prev.count += 1 }"),
               XObject.class);

       results.forEach(new Consumer() {
           @Override
           public void accept(Object o) {
               System.out.println(o);
           }
       });
       // 结果:
       // XObject [x=1.0 count = 2.0]
       // XObject [x=2.0 count = 1.0]
       // XObject [x=3.0 count = 3.0]
       // 注意:笔者运行的 MongoDB 版本为 3.4.15,在 5.0.14 版本运行抛出如下错误:
       // com.mongodb.MongoCommandException: Command failed with error 59 (CommandNotFound): 'no such command: 'group''
       // on server localhost:27017. The full response is {"ok": 0.0, "errmsg": "no such command: 'group'", "code": 59,
       // "codeName": "CommandNotFound"}
   }


   @Test
   public void group2() {
       GroupByResults<XObject> results = mongoTemplate.group(where("x").gt(0),
               "group_test_collection",
               keyFunction("classpath:keyFunction.js").initialDocument("{ count: 0 }")
                       .reduceFunction("classpath:groupReduce.js"), XObject.class);

       results.forEach(new Consumer() {
           @Override
           public void accept(Object o) {
               System.out.println(o);
           }
       });
       // 结果:
       // XObject [x=1.0 count = 2.0]
       // XObject [x=2.0 count = 1.0]
       // XObject [x=3.0 count = 3.0]
   }

}
说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号