如何在 Spring 数据 Mongo 中的聚合投影字段中嵌套字段
How do I nest a field within an Aggregation projection field in Spring Data Mongo
手写时,聚合管道中的 $project
步骤如下所示:
{
"$project":{
"DRIVE":{
"componentSummary":{"manufacturer" : "$_id.DRIVE_manufacturer"},
"componentCount":"$_id.DRIVE_componentCount"
},
"hostnames":1,
"_id":0
}
}
我知道我可以使用 ProjectionOperationBulder
创建单层嵌套(使用 builder.nested),例如:
{
"$project":{
"DRIVE":{
"manufacturer":"$_id.DRIVE_manufacturer"
},
"hostnames":1,
"_id":0
}
}
但我似乎无法弄清楚如何嵌套另一个层次,因为 Field
接口只允许一个字符串名称和一个字符串目标,而不是能够定义另一个 Field
作为目标。
谢谢!
对于其他为此苦苦挣扎的人 -- Spring 数据 Mongo 在撰写本文时(稳定版本 1.9.5)不支持多层嵌套。但是,从 1.9.3 开始,它确实支持 custom AggregationExpressions 允许您自己定义行为。请注意,如果您沿着这条路走下去,您将不得不主要手动为查询构建 JSON。我的实现非常快速和肮脏,但这里仅供参考。
protected class NestedField implements Field {
private String name;
private List<Field> fields;
public NestedField(String name, List<Field> fields) {
this.name = name;
this.fields = fields;
}
public List<Field> getFields() {
return fields;
}
@Override
public String getName() {
return name;
}
private String escapeSystemVariables(String fieldTarget) {
if (fieldTarget.startsWith("_id")) {
return StringUtils.prependIfMissing(fieldTarget, "$");
} else {
return fieldTarget;
}
}
private String encloseStringInQuotations(String quotable) {
return JSON.serialize(quotable);
}
private String buildSingleFieldTarget(Field field) {
if (field instanceof NestedField) {
return String.join(":", encloseStringInQuotations(field.getName()), field.getTarget());
}
return String.join(":", encloseStringInQuotations(field.getName()), encloseStringInQuotations(escapeSystemVariables(
field.getTarget())));
}
private String buildFieldTargetList(List<Field> fields) {
List<String> fieldStrings = new ArrayList<>();
fields.forEach(field -> {
fieldStrings.add(buildSingleFieldTarget(field));
});
return Joiner.on(",").skipNulls().join(fieldStrings);
}
@Override
public String getTarget() {
// TODO Auto-generated method stub
return String.format("{%s}", buildFieldTargetList(fields));
}
@Override
public boolean isAliased() {
return true;
}
}
protected class NestedProjection implements AggregationExpression {
private List<Field> projectedFields;
public NestedProjection(List<Field> projectedFields) {
this.projectedFields = projectedFields;
}@Override
public DBObject toDbObject(AggregationOperationContext context) {
DBObject projectionExpression = new BasicDBObject();
for(Field f : projectedFields) {
//this is necessary because if we just put f.getTarget(), spring-mongo will attempt to JSON-escape the string
DBObject target = (DBObject) com.mongodb.util.JSON.parse(f.getTarget());
projectionExpression.put(f.getName(), target);
}
return projectionExpression;
}
}
手写时,聚合管道中的 $project
步骤如下所示:
{
"$project":{
"DRIVE":{
"componentSummary":{"manufacturer" : "$_id.DRIVE_manufacturer"},
"componentCount":"$_id.DRIVE_componentCount"
},
"hostnames":1,
"_id":0
}
}
我知道我可以使用 ProjectionOperationBulder
创建单层嵌套(使用 builder.nested),例如:
{
"$project":{
"DRIVE":{
"manufacturer":"$_id.DRIVE_manufacturer"
},
"hostnames":1,
"_id":0
}
}
但我似乎无法弄清楚如何嵌套另一个层次,因为 Field
接口只允许一个字符串名称和一个字符串目标,而不是能够定义另一个 Field
作为目标。
谢谢!
对于其他为此苦苦挣扎的人 -- Spring 数据 Mongo 在撰写本文时(稳定版本 1.9.5)不支持多层嵌套。但是,从 1.9.3 开始,它确实支持 custom AggregationExpressions 允许您自己定义行为。请注意,如果您沿着这条路走下去,您将不得不主要手动为查询构建 JSON。我的实现非常快速和肮脏,但这里仅供参考。
protected class NestedField implements Field {
private String name;
private List<Field> fields;
public NestedField(String name, List<Field> fields) {
this.name = name;
this.fields = fields;
}
public List<Field> getFields() {
return fields;
}
@Override
public String getName() {
return name;
}
private String escapeSystemVariables(String fieldTarget) {
if (fieldTarget.startsWith("_id")) {
return StringUtils.prependIfMissing(fieldTarget, "$");
} else {
return fieldTarget;
}
}
private String encloseStringInQuotations(String quotable) {
return JSON.serialize(quotable);
}
private String buildSingleFieldTarget(Field field) {
if (field instanceof NestedField) {
return String.join(":", encloseStringInQuotations(field.getName()), field.getTarget());
}
return String.join(":", encloseStringInQuotations(field.getName()), encloseStringInQuotations(escapeSystemVariables(
field.getTarget())));
}
private String buildFieldTargetList(List<Field> fields) {
List<String> fieldStrings = new ArrayList<>();
fields.forEach(field -> {
fieldStrings.add(buildSingleFieldTarget(field));
});
return Joiner.on(",").skipNulls().join(fieldStrings);
}
@Override
public String getTarget() {
// TODO Auto-generated method stub
return String.format("{%s}", buildFieldTargetList(fields));
}
@Override
public boolean isAliased() {
return true;
}
}
protected class NestedProjection implements AggregationExpression {
private List<Field> projectedFields;
public NestedProjection(List<Field> projectedFields) {
this.projectedFields = projectedFields;
}@Override
public DBObject toDbObject(AggregationOperationContext context) {
DBObject projectionExpression = new BasicDBObject();
for(Field f : projectedFields) {
//this is necessary because if we just put f.getTarget(), spring-mongo will attempt to JSON-escape the string
DBObject target = (DBObject) com.mongodb.util.JSON.parse(f.getTarget());
projectionExpression.put(f.getName(), target);
}
return projectionExpression;
}
}