不能包含具有 Spring 数据聚合的嵌套字段



我在聚合中包含和排除嵌套字段时遇到问题。我有一个包含嵌套对象的集合,因此当我尝试通过仅包含嵌套对象的某些字段来构建查询时,它仅适用于查询,但不适用于聚合

这是我收藏的简单预览

{
"id": "1234",
"name": "place name",
"address": {
"city": "city name",
"gov": "gov name",
"country": "country name",
"location": [0.0, 0.0],
//some other data
},
//some other data
}

当我使用查询配置我的字段时,它可以工作

query.fields().include("name").include("address.city").include("address.gov")

此外,当我使用 shell 进行聚合时,它可以工作

db.getCollection("places").aggregate([
{ $project: {
"name": 1,
"address.city": 1,
"address.gov": 1
} },
])

但它不适用于春季的聚合

val aggregation = Aggregation.newAggregation(
Aggregation.match(criteria)
Aggregation.project().andInclude("name").andInclude("address.city").andInclude("address.gov")
)

这种带有 spring 的聚合始终将地址字段返回为 null,但是当我放置没有嵌套字段的"地址"字段时,结果将包含完整的地址对象,而不仅仅是我想要包含的嵌套字段。

有人可以告诉我如何解决这个问题吗?

我找到了一个解决方案,它是通过使用nested()函数

val aggregation = Aggregation.newAggregation(
Aggregation.match(criteria)
Aggregation.project().andInclude("name")
.and("address").nested(Fields.fields("address.city", "address.gov"))
)

但它仅适用于硬编码字段。因此,如果您想要一个函数来传递要包含或排除的字段列表,则可以使用此解决方案

fun fieldsConfig(fields : List<String>) : ProjectionOperation
{
val mainFields = fields.filter { !it.contains(".") }
var projectOperation = Aggregation.project().andInclude(*mainFields.toTypedArray())
val nestedFields = fields.filter { it.contains(".") }.map { it.substringBefore(".") }.distinct()
nestedFields.forEach { mainField ->
val subFields = fields.filter { it.startsWith("${mainField}.") }
projectOperation = projectOperation.and(mainField).nested(Fields.fields(*subFields.toTypedArray()))
}
return projectOperation
}

此解决方案的问题在于有大量代码、对象的内存分配以及包含字段的配置。此外,它仅适用于包含,如果您使用它来排除字段,它会引发异常。此外,它不适用于文档的最深层字段。

因此,我实现了一个更简单、更优雅的解决方案,它涵盖了大多数包含和排除字段的情况。

此生成器类允许您创建包含要包含或排除的字段的对象。

class DbFields private constructor(private val list : List<String>, val include : Boolean) : List<String>
{
override val size : Int get() = list.size
//overridden functions of List class.

/**
* the builder of the fields.
*/
class Builder
{
private val list = ArrayList<String>()
private var include : Boolean = true

/**
* add a new field.
*/
fun withField(field : String) : Builder
{
list.add(field)
return this
}

/**
* add a new fields.
*/
fun withFields(fields : Array<String>) : Builder
{
fields.forEach {
list.add(it)
}
return this
}

fun include() : Builder
{
include = true
return this
}

fun exclude() : Builder
{
include = false
return this
}

fun build() : DbFields
{
if (include && !list.contains("id"))
{
list.add("id")
}
else if (!include && list.contains("id"))
{
list.remove("id")
}
return DbFields(list.distinct(), include)
}
}
}

构建字段配置

val fields = DbFields.Builder()
.withField("fieldName")
.withField("fieldName")
.withField("fieldName")
.include()
.build()

您可以将此对象传递到存储库以配置包含或排除。

我还创建了此类,以使用将转换为自定义聚合操作的原始文档配置包含和排除。

class CustomProjectionOperation(private val fields : DbFields) : ProjectionOperation()
{
override fun toDocument(context : AggregationOperationContext) : Document
{
val fieldsDocument = BasicDBObject()
fields.forEach {
fieldsDocument.append(it, fields.include)
}
val operation = Document()
operation.append("$project", fieldsDocument)
return operation
}
}

现在,您只需在聚合中使用此类

class RepoCustomImpl : RepoCustom
{
@Autowired
private lateint mongodb : MongoTemplate

override fun getList(fields : DbFields) : List<Result>
{
val aggregation = Aggregation.newAggregation(
CustomProjectionOperation(fields)
)
return mongodb.aggregate(aggregation, Result::class.java, Result::class.java).mappedResults
}
}

最新更新