java中的MongoDB聚合:如何获得总记录数和分页结果



我有一个搜索条件,其中我必须对搜索结果以及集合中的记录总数进行分页。假设在一个10条记录的集合中,我只想得到5条记录以及总记录数。得到的数据,我想把它们推送到具有count和searchResult属性的单独对象中。记录总数必须映射到计数,并将记录分页到searchResult。我已经应用了聚合,除了包含CountOperation和ProjectOperation之外,它运行得很好。当我在聚合中添加countOperation和ProjectOperation时,它会给出无效引用"_id!"异常。预期的查询是这样的。

db.customer.aggregate([
{
$facet:{
searchResult:[{$match:{"name" : { "$regex" : "xyz", "$options" : "i" }}}],
count: [{ $count: 'count' }]
}
}

])

输出将是这样的。

[
{
"searchResult":[{...},{...},{...}, ...],
"count":[{"count":100}]
}
]

搜索逻辑:

public List<SampleSearchResult> findListByRequest(ListRequest queryParams, Class<T> clazz) {
String collectionName = mongoTemplate.getCollectionName(clazz);
MatchOperation matchOperation = getMatchOperation(queryParams);
SortOperation sortOperation = getSortOperation(queryParams);
SkipOperation skipOperation = Aggregation.skip((long) queryParams.getPageNumber() * queryParams.getSize());
LimitOperation limitOperation = Aggregation.limit(queryParams.getSize());
CountOperation countOperation = Aggregation.count().as("count");
ProjectionOperation projectionOperation = getProjectionOperation();
AggregationResults<SampleSearchResult> results = mongoTemplate
.aggregate(Aggregation.newAggregation(matchOperation, sortOperation, skipOperation, limitOperation, countOperation, projectionOperation ), collectionName, SampleSearchResult.class);
return (List<SampleSearchResult>) results.getMappedResults();
}

投影操作逻辑

private ProjectionOperation getProjectionOperation() {
return Aggregation.project("count").and("_id").previousOperation();
}

分拣操作逻辑:

private SortOperation getSortOperation(ListRequest listRequest) {
// setting defaults
if (StringUtils.isEmpty(listRequest.getSortBy())) {
listRequest.setSortBy("_id");
listRequest.setAsc(false);
}
Sort sort = listRequest.isAsc() ? new Sort(Direction.ASC, listRequest.getSortBy())
: new Sort(Direction.DESC, listRequest.getSortBy());
return Aggregation.sort(sort);
}

MatchOperation逻辑:

private MatchOperation getMatchOperation(ListRequest listRequest) {
Criteria criteria = new Criteria();
// build match operation logic with listRequest parameters
return Aggregation.match(criteria);
}

将保存聚合结果的结果对象

public class SampleSearchResult {
private List<Object> searchResult;
private int count;
public List<Object> getSearchResult() {
return searchResult;
}
public void setSearchResult(List<Object> searchResult) {
this.searchResult = searchResult;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}

}

我需要正确地编写CountOperation和ProjectionOperation来将数据映射到SampleSearchResult,但由于我是MongoDB操作的新手,所以我的效率不高。

这可能来得很晚,但让我把我的答案放在这里,看看谁将来可能会遇到同样的麻烦。正确的方法是真正使用这里描述的facet。

我可以在Java中使用facet,但将查询结果映射到pojo类对我来说也是一个问题。

首先看问题中的预期输出:

[
{
"searchResult":[{...},{...},{...}, ...],
"count":[{"count":100}]
}
]

一个适合这个输出的pojo必须这样建模:

@Getter //lombok stuff to create getters and setters
@Setter
public class CustomerSearchResult {
private List<CustomerData> searchResult;
private List<CountDto> count;
}

这里是CountDto.java

@Setter
@Getter
public class CountDto {
private Long count;
}

这里是CustomerData.java

@Getter
@Setter
public class CustomerData {

private Long dateCreated;
private String Id;
private String firstName;
private String lastName;
}

接下来,MongoClient必须使用自定义或默认代码注册表进行实例化,如下所示:

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
public class MongoSource {
private MongoClient mongoClient;
public MongoClient createClient(){
String connectionString = "your.database.connection.string";
ConnectionString connection = new ConnectionString(connectionString);
CodecRegistry defaultCodec = MongoClientSettings.getDefaultCodecRegistry();
CodecRegistry fromProvider = fromProviders(PojoCodecProvider.builder().automatic(true).build());
CodecRegistry pojoCodecRegistry = fromRegistries(defaultCodec, fromProvider);
MongoClientSettings.Builder builder = MongoClientSettings.builder();
builder.applyConnectionString(connection);
MongoClientSettings settings = builder.codecRegistry(pojoCodecRegistry).build();
mongoClient =  MongoClients.create(settings);
return mongoClient;
}

public MongoClient getMongoClient(){
return mongoClient;
}
}

使用MongoJava驱动程序4.0.X

不确定问题中使用的mongo驱动程序或第三方API的版本,但我认为标准的使用已经被弃用,或者从来不是mongo API的一部分。但这就是我使用MongoJavaDriver4.0.x 实现这一目标的方式

为了使用facet实现具有分页结果和总计数的搜索,需要构建两个独立的管道

  • 使用排序、限制和跳过进行搜索

  • 另一个做实际计数的总结果没有限制并跳过

这两条管道最终将用于构造与聚合API一起使用的facet。

考虑下面用于定义各个查询字段的pojo

import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class SearchQuery {
String firstName;
String lastName;
int pageNumber;
int pageSize;
}

这是执行搜索并返回CustomerSearchResult intsance的最后一个代码段,该代码段包含计数和pageSizepageNumber定义的分页结果。

import java.util.*;
import static com.mongodb.client.model.Aggregates.*;
import static com.mongodb.client.model.Filters.*;
import static com.mongodb.client.model.Projections.fields;
import static com.mongodb.client.model.Projections.include;
import org.apache.commons.lang3.StringUtils;
public CustomerSearchResult findListByRequest(SearchQuery queryParam){
MongoSource mongoSource = new MongoSource();
MongoDatabase database = mongoSource.createClient().getDatabase("customers_db");
MongoCollection<CustomerSearchResult> collection = database.getCollection("customer", CustomerSearchResult.class);

List<Bson> queryPipeline = new ArrayList<>();
List<Bson> countPipeline = new ArrayList<>();
List<Bson> andMatchFilter = new ArrayList<>();
//you might want to check for null before using the fields in the query param object
andMatchFilter.add(regex("firstName", Pattern.compile(queryParam.getFirstName(), Pattern.CASE_INSENSITIVE)));
andMatchFilter.add(regex("lastName", Pattern.compile(queryParam.getLastName(), Pattern.CASE_INSENSITIVE)));
if(queryParam.getPageNumber() == 0){
queryParam.setPageNumber(1);
}
if(queryParam.getPageSize() == 0){
queryParam.setPageSize(30);
}
queryPipeline.add(match(and(andMatchFilter)));
queryPipeline.add(sort(eq("dateCreated", -1)));
queryPipeline.add(skip(queryParam.getPageSize() * (queryParam.getPageNumber() - 1)));
queryPipeline.add(limit(queryParam.getPageSize()));
queryPipeline.add(project(fields(include("Id","firstName","lastName"))));
countPipeline.add(match(and(andMatchFilter)));
countPipeline.add(count());
Facet resultFacet = new Facet("searchResult", queryPipeline);
Facet totalDocFacet = new Facet("count", countPipeline);
return collection.aggregate(Collections.singletonList(facet(resultFacet, totalDocFacet))).first();
}

请注意,CustomerSearchResult字段与分别为每个管道定义的facet的名称相同。这将允许定义的代码注册表将输出文档正确映射到您的pojo类(CustomerSearchResult(

然后你可以这样做访问计数:

SearchQuery searchQuery = new SearchQuery();
searchQuery.setPageNumber(1);
searchQuery.setPageSize(15);
searchQuery.setFirstName("John");
searchQuery.setLastName("Doe");
CustomerSearchResult result = findListByRequest(query);
long count = result.getCount()!= null? result.getCount().get(0).getCount() : 0;
List<CustomerData> data = result.getSearchResult();

最新更新