You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Spring Data MongoDB实现多条件查询与匹配数组元素投影

问题:Spring Data MongoDB 多条件查询仅返回匹配数组元素失败

我刚开始学习Spring Data MongoDB,想要实现多条件查询:查询满足指定_id且数组中存在特定元素的文档,并且仅返回数组中匹配的元素。

在MongoDB CLI中,我用以下查询得到了预期结果:

db.ticket_detail_comments.find(
    {
        histories: {
            $elemMatch: {
                content: "44444455666"
            }
        },
        _id : "df05532f-8893-4802-8710-ab92056c9c77"
    },
    { "histories.$": 1, name: 1 }
)

返回结果:

{
    "_id": "df05532f-8893-4802-8710-ab92056c9c77",
    "histories": [
        {
            "content": "44444455666",
            "createdAt": {
                "$date": {
                    "$numberLong": "1664970753740"
                }
            }
        }
    ]
}

但用Spring Data MongoDB的MongoTemplate实现时,返回的数组包含所有元素,不符合预期。我做了简化Demo:

集合数据:

{
  "_id": "dad9db98-241b-40fa-aab4-af1671f97631",
  "data": "demo",
  "children": [
    {
      "name": "tim",
      "age": 19
    },
    {
      "name": "alex",
      "age": 18
    }
  ],
  "_class": "com.example.demo.Data"
}

CLI查询:

db.data.find(
    {
        children: {
            $elemMatch: {
                name: "alex"
            }
        },
        _id : "dad9db98-241b-40fa-aab4-af1671f97631"
    },
    { "children.$": 1}
)

返回正确结果:

{ _id: 'dad9db98-241b-40fa-aab4-af1671f97631',
  children: [ { name: 'alex', age: 18 } ] }

我的Java代码:

package com.example.demo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.UUID;

@SpringBootApplication
public class DemoApplication {

    private final MongoTemplate template;

    public DemoApplication(MongoTemplate template) {
        this.template = template;
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @PostConstruct
    public void init() {
        Data.Child child1 = Data.Child.builder()
                .name("alex")
                .age(18)
                .build();

        Data.Child child2 = Data.Child.builder()
                .name("tim")
                .age(19)
                .build();
        final String id = UUID.randomUUID().toString();
        Data data = Data.builder()
                .id(id)
                .data("demo")
                .children(List.of(child2, child1))
                .build();


        template.save(data, "data");
        Query query = Query.query(
                Criteria.where("_id").is(id).and("children")
                        .elemMatch(Criteria.where("name").is("alex"))
        );
        query.fields().position("children", 1);
        var r = template.findOne(query, Data.class, "data");
        System.out.printf("DEBUG");
    }

}

@lombok.Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
class Data {

    private String id;

    private String data;

    private List<Child> children;

    @lombok.Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    static class Child {
        private String name;
        private Integer age;
    }
}

但返回的Data对象中children列表包含2个元素,请问正确的写法是什么?


解决方案

问题出在你使用了query.fields().position("children", 1),这个方法对应MongoDB的$position操作符,作用是指定数组元素插入的位置,并非用来投影匹配的数组元素。

要实现和CLI中"children.$": 1相同的投影效果,需要用include方法指定投影字段为children.$:

修改查询部分的代码:

Query query = Query.query(
        Criteria.where("_id").is(id)
                .and("children").elemMatch(Criteria.where("name").is("alex"))
);
// 替换position为include指定"children.$"
query.fields().include("children.$");
// 如果需要保留其他字段(比如data),可以继续添加include
// query.fields().include("children.$").include("data");
var r = template.findOne(query, Data.class, "data");

修改后,查询会只返回children数组中匹配elemMatch条件的第一个元素,和CLI查询效果一致。

另外,也可以用更清晰的Criteria组合写法:

Criteria criteria = new Criteria();
criteria.and("_id").is(id);
criteria.and("children").elemMatch(Criteria.where("name").is("alex"));

Query query = new Query(criteria);
query.fields().include("children.$");

内容的提问来源于stack exchange,提问作者Tung Nguyen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 10:30:29