java - 无法包含带有 Spring 数据聚合的嵌套字段

标签 java mongodb spring-data aggregation-framework

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

这是我的 Collection 的简单预览

{
    "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
    } },
])

但它不适用于 spring 中的聚合

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

关于java - 无法包含带有 Spring 数据聚合的嵌套字段,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61951653/

相关文章:

javascript - 需要建议 : How to properly connect React to MongoDB

mysql - Spring Data JPA 中的 MIN/MAX 函数与 Amazon Aurora DB(每组最大 n 次查询)

java - 如何在Javafx中的TreeView中设置单元格的缩进

java - JPA 2 标准 API : why is isNull being ignored when in conjunction with equal?

ruby-on-rails - 将大 json 文件存储在数据库中的最佳方式(postgres 或 mongo)

java - 将 bigint postgres 数据类型隐式转换为 Java Long

spring-data - 如何使用 Spring Data 中的 exists 关键字来检查实体是否存在?

java - 为什么 gc.clearRect 不会清除 Canvas ?

基于 Java 2D 方 block 的游戏碰撞(上/下/左/右)

c# - 使用 C# 的 MongoDb 响应缓慢