如何在Spring Data中执行Mongo聚合查询?

17

这是我第一次在Java中使用Mongo,我在使用聚合查询时遇到了一些问题。我可以在扩展MongoRepository<T, ID>的存储库接口中使用@Query注释来执行一些简单的查询。当您在Spring-Data中进行长时间的聚合时,知道采取哪种方法将会很有帮助。

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 } }
])

1
你需要使用 mongoTemplate 或者原生的 MongoDB 驱动程序。可以参考这篇文章 - Valijon
可能是 https://dev59.com/TmQn5IYBdhLWcg3wNk7t 的重复问题。 - krishna Prasad
2个回答

30
你可以实现 AggregationOperation 并编写自定义聚合操作查询,然后使用 MongoTemplate 执行你在mongo shell中执行的任何mongo shell查询,如下所示:

自定义聚合操作

import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;

public class CustomAggregationOperation implements AggregationOperation {

  private String jsonOperation;

  public CustomAggregationOperation(String jsonOperation) {
    this.jsonOperation = jsonOperation;
  }

  @Override
  public org.bson.Document toDocument(AggregationOperationContext aggregationOperationContext) {
    return aggregationOperationContext.getMappedObject(org.bson.Document.parse(jsonOperation));
  }
}

任何Mongo Shell聚合查询执行器

import org.springframework.beans.factory.annotation.Autowired;
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.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.TypedAggregation;
import org.springframework.stereotype.Service;
import sample.data.mongo.models.Course;

@Service
public class LookupAggregation {

  @Autowired
  MongoTemplate mongoTemplate;

  public void LookupAggregationExample() {

    AggregationOperation unwind = Aggregation.unwind("studentIds");

    String query1 = "{$lookup: {from: 'student', let: { stuId: { $toObjectId: '$studentIds' } },"
        + "pipeline: [{$match: {$expr: { $eq: [ '$_id', '$$stuId' ] },},}, "
        + "{$project: {isSendTemplate: 1,openId: 1,stu_name: '$name',stu_id: '$_id',},},], "
        + "as: 'student',}, }";

    TypedAggregation<Course> aggregation = Aggregation.newAggregation(
        Course.class,
        unwind,
        new CustomAggregationOperation(query1)
    );

    AggregationResults<Course> results =
        mongoTemplate.aggregate(aggregation, Course.class);
    System.out.println(results.getMappedResults());
  }
}

欲了解更多详情,请查阅Github代码库中的类:CustomAggregationOperationLookupAggregation

其他方法也使用MongoTemplate

#1. 为您的Model Post定义自定义代码的接口:

interface CustomPostRepository {
     List<Post> yourCustomMethod();
}

#2. 添加此类的实现,并遵循命名约定以确保我们可以找到该类。

class CustomPostRepositoryImpl implements CustomPostRepository {

    @Autowired
    private MongoOperations mongoOperations;

    public List<Post> yourCustomMethod() {

      // custom match queries here
      MatchOperation match = null;
      // Group by , Lookup others stuff goes here
      // For details: https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/aggregation/Aggregation.html

      Aggregation aggregate = Aggregation.newAggregation(match);

      AggregationResults<Post> orderAggregate = mongoOperations.aggregate(aggregate,
                      Post.class, Post.class);
      return orderAggregate.getMappedResults();

    }
}

#3. 现在让您的基础存储库接口扩展自定义存储库接口,基础设施将自动使用您的自定义实现:

interface PostRepository extends CrudRepository<Post, Long>, CustomPostRepository {

}

29
尽管这是旧帖子,但我希望找到这个帖子的人现在可以放心地在MongoRepository中进行多阶段/管道聚合(不太确定它被称为什么)。 因为我也在苦苦寻找MongoRepository中聚合的线索和示例,不使用mongo模板。 但现在,我能够按照Spring文档中所述进行聚合管道,请点击这里。 我的聚合在mongoshell中看起来像这样:
db.getCollection('SalesPo').aggregate([
    {$project: {
        month: {$month: '$poDate'},
        year: {$year: '$poDate'},
        amount: 1,
        poDate: 1
     }},
      {$match: {$and : [{year:2020} , {month:7}] 
     }}
      ,
      {$group: { 
          '_id': {
            month: {$month: '$poDate'},
            year: {$year: '$poDate'} 
          },
          totalPrice: {$sum: {$toDecimal:'$amount'}},
          }
      },
    {$project: {
        _id: 0,
        totalPrice: {$toString: '$totalPrice'}
     }}
 ])

当我将它转换为@Aggregation注释时,MongoRepository变成了下面这样(我正在移除撇号并替换方法参数):

当我将其转换为@Aggregation注释时,MongoRepository会变成以下内容(我将撇号移除并将其替换为方法参数):

@Repository
public interface SalesPoRepository extends MongoRepository<SalesPo, String> {

@Aggregation(pipeline = {"{$project: {\n" +
        "        month: {$month: $poDate},\n" +
        "        year: {$year: $poDate},\n" +
        "        amount: 1,\n" +
        "        poDate: 1\n" +
        "     }}"
        ,"{$match: {$and : [{year:?0} , {month:?1}] \n" +
        "     }}"
        ,"{$group: { \n" +
        "          '_id': {\n" +
        "            month: {$month: $poDate},\n" +
        "            year: {$year: $poDate} \n" +
        "          },\n" +
        "          totalPrice: {$sum: {$toDecimal:$amount}},\n" +
        "          }\n" +
        "      }"
    ,"{$project: {\n" +
        "        _id: 0,\n" +
        "        totalPrice: {$toString: $totalPrice}\n" +
        "     }}"})
    AggregationResults<SumPrice> sumPriceThisYearMonth(Integer year, Integer month);

我的文档看起来像这样:

@Document(collection = "SalesPo")
@Data
public class SalesPo {
  @Id
  private String id;
  @JsonSerialize(using = LocalDateSerializer.class)
  private LocalDate poDate;
  private BigDecimal amount;
}

并且有一个SumPrice类用于保存投影结果:

@Data
public class SumPrice {
  private BigDecimal totalPrice;
}

我希望这个答案能够帮助那些试图在mongorepository中进行聚合操作而不使用mongotemplate的人


你如何处理空参数? - saran3h
@saran3h 在 MongoDB 中,sum 聚合操作默认将 null 值(或非数字值)处理为零。请参阅 https://docs.mongodb.com/manual/reference/operator/aggregation/sum - Yosep G
2
嗨,感谢您的回答,但我有些困惑: 1.在@Aggregation中编写查询JSON是否有更好的方法?使用换行模式和引号解析查询字符串非常麻烦。 - Anoman.M
  1. 是否有可能不定义一个类来检索聚合结果?因为结果可能包含许多引用文档,我必须定义它们所有才能做到这一点。例如,我已经定义了一些实体映射到集合,并且它们通过使用对象ID数组相互引用。我进行聚合以返回一个完整匹配的文档,该文档将替换对象ID为真实文档。为了检索最终文档,我必须重新定义整个实体树,除了将对象ID数组替换为嵌套实体数组之外没有任何更改。是否有更好的方法来解决这个问题?
- Anoman.M
更新:对于问题2,我使用JSONObject、Object或BasicDBObject来检索聚合结果。像这样:AggregationResults<JSONObject> result = someRepository.doAggregate(id) - Anoman.M
我得到了一个奇怪的结果。我得到了包含2个项目的aggregateResult,但是这两个项目都是空的。尝试使用JSONObject,也没有起作用。 - Jay Patel - PayPal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接