如何用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
相关产品推荐
相关产品推荐

