如何在Spring Data MongoDB中实现聚合查询? [英] How to implement aggregation query in Spring Data MongoDB?

查看:58
本文介绍了如何在Spring Data MongoDB中实现聚合查询?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是Spring data MongoDB的新手,我正在尝试用Spring data MongoDB用Java实现聚合查询。我已尝试从此问题进行搜索,并使用MongoTemplate解决了它,但仍未找到结果。

我的数据格式:

[{ 
    "_id" : ObjectId("5e1aea6c275360baf96bac29"), 
    "title" : "postim", 
    "upvotesBy" : [
        "5e18b4c12753608718dfa007", 
        "5e19ac0f5161a4994ded1f35"
    ], 
    "file" : "test", 
    "description" : "description", 
    "postedBy" : "5e18b4c12753608718dfa007", 
    "createdAt" : ISODate("2020-01-12T09:44:12.119+0000"), 
    "_class" : "com.socialnetwork.post.Post"
},
{ 
    "_id" : ObjectId("5e1aeaf8275360bb4bb47325"), 
    "title" : "postim2", 
    "upvotesBy" : [
        "5e18b4c12753608718dfa007", 
        "5e19ac0f5161a4994ded1f35"
    ], 
    "file" : "test2", 
    "description" : "description2", 
    "postedBy" : "5e18b4c12753608718dfa007", 
    "createdAt" : ISODate("2020-01-12T09:46:32.909+0000"), 
    "_class" : "com.socialnetwork.post.Post"
}]

我的问题:

db.post.aggregate([
    {
      $match: {}
    },
    {
      $lookup: {
        from: "users",
        localField: "postedBy",
        foreignField: "_id",
        as: "user"
      }
    },
    {
      $group: {
        _id: {
          username: "$user.name",
          title: "$title",
          description: "$description",
          upvotes: { $size: "$upvotesBy" },
          upvotesBy: "$upvotesBy",
          isUpvoted: { $in: [req.query.userId, "$upvotesBy"] },
          isPinned: {
            $cond: {
              if: { $gte: [{ $size: "$upvotesBy" }, 3] },
              then: true,
              else: false
            }
          },
          file: "$file",
          createdAt: {
            $dateToString: {
              format: "%H:%M %d-%m-%Y",
              timezone: "+01",
              date: "$createdAt"
            }
          },
          id: "$_id"
        }
      }
    },
    { $sort: { "_id.isPinned": -1, "_id.createdAt": -1 } }
])

这是我在我的Java后端中使用的查询,我可以用Mongoose很容易地完成这项工作。然而,我对它的Java实现遇到了一些困难。

private LookupOperation getLookupOperation() {
        return LookupOperation.newLookup().from("user")
                .localField("postedBy")
                .foreignField("_id")
                .as("user");
    }

    @Override
    public List<PostSummary> aggregate() {
        LookupOperation lookupOperation = getLookupOperation();
        return mongoTemplate.aggregate(Aggregation.newAggregation(lookupOperation, Aggregation.group("id")
                .addToSet("user.name").as("username")
                .addToSet("title").as("title")
                .addToSet("description").as("description")
                .addToSet("id").as("id")
                .push("upvotesBy").as("upvotesBy")
                .addToSet("file").as("file")
                .addToSet("createdAt").as("createdAt")
        ), Post.class, PostSummary.class).getMappedResults();
}

当我尝试运行此命令时,收到以下错误:

"Cannot convert [] of type class java.util.ArrayList into an instance of class java.lang.Object! Implement a custom Converter<class java.util.ArrayList, class java.lang.Object> and register it with the CustomConversions. Parent object was: com.socialnetwork.post.PostSummary@7159d908"

当我从组聚合中删除.addToSet("user.name").as("username")时,我也从.push("upvotesBy").as("upvotesBy")收到错误,因为它无法转换[] of type class java.util.ArrayList into an instance of class java.lang.String

Post类和PostSummary类的实现也很简单:

Post.java

@Document
public class Post {
    @Id
    private String id;
    private String title;
    private List<String> upvotesBy;
    private String file;
    private String description;
    private String postedBy;
    private Date createdAt = new Date();

//  ... Getters and Setters for each field
}

PostSummary.java

public class PostSummary {
    private String username;
    private String title;
    private String description;
    private List<String> upvotesBy;
    private String file;
    private String createdAt;
    private String id;

//... Getters and Setters for the class
}

我还需要实现查询的isUpvotedisPinned部分,但了解如何处理第一个问题将是一个很好的开始。

编辑:我想要的输出:

[
{
   "username" : "user1", 
   "title" : "postim2", 
   "upvotesBy" : [
      "5e18b4c12753608718dfa007", 
      "5e19ac0f5161a4994ded1f35"
   ],
   "file": "file1",
   id: "5e18b4c12753608718dber01"
   ... Other fields of the original post
},
{
   "username" : "user2", 
   "title" : "postim2", 
   "upvotesBy" : [
      "5e18b4c12753608718dfa007", 
      "5e19ac0f5161a4994ded1f35"
   ],
   id: "5e18b4c12753608718dber02",
   "file": "file2",
   ... Other fields of the original post
}
]

因此,从查找操作中,我只需要获取用户名。

推荐答案

开始吧

我们需要更新您的聚合才能使其工作。

错误:

  1. users_idObjectId类型,但您在帖子中存储为String,因此$lookup应改为Uncorrelated sub-queries
  2. 我们将$group替换为更适合的‘$addFields’
  3. 我们添加作为最后阶段的$project运算符,以排除所有未使用的字段。

db.post.aggregate([
  {
    $match: {}
  },
  {
    $lookup: {
      from: "users",
      let: {
        postedBy: "$postedBy"
      },
      pipeline: [
        {
          $match: {
            $expr: {
              $eq: [
                {
                  "$toString": "$_id"
                },
                "$$postedBy"
              ]
            }
          }
        }
      ],
      as: "user"
    }
  },
  {
    $unwind: "$user"
  },
  {
    $addFields: {
      id: {
        $toString: "$_id"
      },
      username: "$user.name",
      upvotes: {
        $size: "$upvotesBy"
      },
      isUpvoted: {
        $in: [
          "5e18b4c12753608718dfa007",
          "$upvotesBy"
        ]
      },
      isPinned: {
        $cond: [
          {
            $gte: [
              {
                $size: "$upvotesBy"
              },
              3
            ]
          },
          true,
          false
        ]
      },
      createdAt: {
        $dateToString: {
          format: "%H:%M %d-%m-%Y",
          timezone: "+01",
          date: "$createdAt"
        }
      }
    }
  },
  {
    $sort: {
      "isPinned": -1,
      "createdAt": -1
    }
  },
  {
    $project: {
      _id: 0,
      user: 0,
      upvotesBy: 0,
      _class: 0
    }
  }
])

现在,我们将该查询转换为Spring-data语法。

Java实现

package postman;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.project;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.sort;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.unwind;

import java.util.Arrays;
import java.util.List;

import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

@Service
public class PostmanService {

    @Autowired
    private MongoTemplate mongoTemplate;

    public List<PostSummary> find(String userId){

        Aggregation aggregation = Aggregation.newAggregation(
            match(new Criteria()),
            //lookup("users", "postedBy", "_id", "user")
            new AggregationOperation() {
                @Override
                public Document toDocument(AggregationOperationContext context) {
                    return new Document("$lookup",
                        new Document("from", "users")
                            .append("let", new Document("postedBy", "$postedBy"))
                            .append("pipeline", Arrays.asList(
                                new Document("$match", 
                                    new Document("$expr", 
                                        new Document("$eq", Arrays.asList(
                                            new Document("$toString", "$_id"),
                                            "$$postedBy"
                                        ))))))
                            .append("as", "user"));
                }
            },
            unwind("$user"),
            new AggregationOperation() {

                @Override
                public Document toDocument(AggregationOperationContext context) {
                    return new Document("$addFields",
                        new Document("id", new Document("$toString", "$_id"))
                        .append("username", "$user.name")
                        .append("upvotes", new Document("$size", "$upvotesBy"))
                        .append("isUpvoted", new Document("$in", Arrays.asList(userId, "$upvotesBy")))
                        .append("isPinned", new Document("$cond", 
                            Arrays.asList(new Document("$gte", 
                                    Arrays.asList(new Document("$size", "$upvotesBy"), 3)), Boolean.TRUE, Boolean.FALSE)))
                        .append("createdAt", new Document("$dateToString", 
                            new Document("format", "%H:%M %d-%m-%Y")
                                .append("timezone", "+01")
                                .append("date", "$createdAt")
                            )));
                }
            },
            sort(Direction.DESC, "isPinned", "createdAt"),
            project().andExclude("user", "_class")
        );

        System.out.println("Aggregation: " + aggregation.toString());

        return mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Post.class), PostSummary.class).getMappedResults();
    }
}

现在,我们调用聚合管道:

List<PostSummary> l = postmanService.find("5e18b4c12753608718dfa007");
for(PostSummary post: l) {
    ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
    System.out.println(ow.writeValueAsString(post));
}

2020-01-12 16:15:22.043  INFO 11148 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-01-12 16:15:22.047  INFO 11148 --- [           main] Postman.PostmanApplication               : Started PostmanApplication in 4.602 seconds (JVM running for 5.301)
Aggregation: { "aggregate" : "__collection__", "pipeline" : [{ "$match" : {}}, { "$lookup" : { "from" : "users", "let" : { "postedBy" : "$postedBy"}, "pipeline" : [{ "$match" : { "$expr" : { "$eq" : [{ "$toString" : "$_id"}, "$$postedBy"]}}}], "as" : "user"}}, { "$unwind" : "$user"}, { "$addFields" : { "id" : { "$toString" : "$_id"}, "username" : "$user.name", "upvotes" : { "$size" : "$upvotesBy"}, "isUpvoted" : { "$in" : ["5e18b4c12753608718dfa007", "$upvotesBy"]}, "isPinned" : { "$cond" : [{ "$gte" : [{ "$size" : "$upvotesBy"}, 3]}, true, false]}, "createdAt" : { "$dateToString" : { "format" : "%H:%M %d-%m-%Y", "timezone" : "+01", "date" : "$createdAt"}}}}, { "$sort" : { "isPinned" : -1, "createdAt" : -1}}, { "$project" : { "user" : 0, "_class" : 0}}]}
2020-01-12 16:15:22.161  INFO 11148 --- [           main] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:2, serverValue:277}] to localhost:27017
{
  "username" : "user1",
  "title" : "postim2",
  "description" : "description2",
  "upvotesBy" : [ "5e18b4c12753608718dfa007", "5e19ac0f5161a4994ded1f35" ],
  "file" : "test2",
  "createdAt" : "10:46 12-01-2020",
  "id" : "5e1aeaf8275360bb4bb47325"
}
{
  "username" : "user1",
  "title" : "postim",
  "description" : "description",
  "upvotesBy" : [ "5e18b4c12753608718dfa007", "5e19ac0f5161a4994ded1f35" ],
  "file" : "test",
  "createdAt" : "10:44 12-01-2020",
  "id" : "5e1aea6c275360baf96bac29"
}

这篇关于如何在Spring Data MongoDB中实现聚合查询?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆