我有一个搜索条件,其中我必须对搜索结果以及集合中的记录总数进行分页。假设在一个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的最后一个代码段,该代码段包含计数和pageSize和pageNumber定义的分页结果。
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();