Open
Description
The @MongoId
annotation is not being taken into account for nested object queries using a projection class that doesn't include the nested object. This causes collection scans and results in query not finding the documents.
Actual: { "someRef.id" : "a"}
Expected: { "someRef._id" : "a"}
Using:
+--- org.springframework.boot:spring-boot-dependencies:3.1.5
| +--- org.springframework.boot:spring-boot-starter-data-mongodb:3.1.5 (c)
| +--- org.springframework.boot:spring-boot-starter:3.1.5 (c)
| +--- org.mongodb:mongodb-driver-sync:4.6.1 (c)
| +--- org.springframework.data:spring-data-mongodb:3.1.5 (c)
Example code:
@SpringBootApplication
@EnableMongoRepositories
public class ProjectionMongoIdApplication implements CommandLineRunner {
@Autowired
MongoTemplate mongoTemplate;
@SuppressWarnings("resource")
public static void main(String[] args) {
SpringApplication.run(IndexIdMappingApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
mongoTemplate.dropCollection(Widget.class);
IndexOperations indexOps = mongoTemplate.indexOps(Widget.class);
indexOps.ensureIndex(new Index("someRef._id", Direction.ASC));
mongoTemplate.save(new Widget("123", new SomeRef("a", "Apple")));
mongoTemplate.save(new Widget("456", new SomeRef("b", "Banana")));
// queries { "someRef._id" : "a"} and uses index for "someRef._id"
mongoTemplate.find(new Query(Criteria.where("someRef.id").is("a")), Widget.class).forEach(w -> {
System.out.printf("query by someRef.id found %s:%s\n", w.id, w.name);
});
// queries { "someRef._id" : "a"} and uses index for "someRef._id"
mongoTemplate.find(new Query(Criteria.where("someRef._id").is("a")), Widget.class).forEach(w -> {
System.out.printf("query by someRef._id found found %s:%s\n", w.id, w.name);
});
// queries { "someRef._id" : "a"} and uses index for "someRef._id"
Query query = new Query(Criteria.where("someRef.id").is("a"));
query.fields().include("name");
mongoTemplate.find(query, Widget.class).forEach(w -> {
System.out.printf("query by someRef.id w include fields found %s:%s\n", w.id, w.name);
});
// BUG: queries { "someRef.id" : "a"}, finds no doc, and results in a collection scan because it can't find index for "someRef.id"
mongoTemplate.find(new Query(Criteria.where("someRef.id").is("a")), WidgetProjection.class).forEach(w -> {
System.out.printf("query by someRef.id w projection class found %s:%s\n", w.id, w.name);
});
// queries { "someRef._id" : "a"} and uses index for "someRef._id"
mongoTemplate.find(new Query(Criteria.where("someRef._id").is("a")), WidgetProjection.class).forEach(w -> {
System.out.printf("query by someRef._id w projection class found %s:%s\n", w.id, w.name);
});
}
}
@Document
class Widget {
@Id
String id;
String name;
SomeRef someRef;
public Widget(String id, SomeRef someRef) {
this.id = id;
this.name = "name-" + id;
this.someRef = someRef;
}
}
@Document(collection = "widget")
class WidgetProjection {
String id;
String name;
// SomeRef someRef; // Adding this makes it work but don't want it on the projection.
}
class SomeRef {
@MongoId(FieldType.IMPLICIT)
String id;
String name;
public SomeRef(String id, String name) {
this.id = id;
this.name = name;
}
}
Logging (logging.level.org.springframework.data.mongodb.core: DEBUG)
... find using query: { "someRef._id" : "a"} fields: Document{{}} for class: class Widget ...
query by someRef.id found 123:name-123
... find using query: { "someRef._id" : "a"} fields: Document{{}} for class: class Widget ...
query by someRef._id found found 123:name-123
... find using query: { "someRef._id" : "a"} fields: Document{{name=1}} for class: class Widget ...
query by someRef.id w include fields found 123:name-123
... find using query: { "someRef.id" : "a"} fields: Document{{}} for class: class WidgetProjection ...
^- BUG: using wrong id and no documents found and collection scan
... find using query: { "someRef._id" : "a"} fields: Document{{}} for class: class WidgetProjection ...
query by someRef._id w projection class found 123:name-123
Profiling:
db.setProfilingLevel(2)
db.getCollection("system.profile").find({"command.find": "widget"}, {"command.filter": 1, planSummary:1})
{
"command" : {
"filter" : {
"someRef._id" : "a"
}
},
"planSummary" : "IXSCAN { someRef._id: 1 }"
}
{
"command" : {
"filter" : {
"someRef._id" : "a"
}
},
"planSummary" : "IXSCAN { someRef._id: 1 }"
}
{
"command" : {
"filter" : {
"someRef._id" : "a"
}
},
"planSummary" : "IXSCAN { someRef._id: 1 }"
}
{
"command" : {
"filter" : {
"someRef.id" : "a"
}
},
"planSummary" : "COLLSCAN"
}
{
"command" : {
"filter" : {
"someRef._id" : "a"
}
},
"planSummary" : "IXSCAN { someRef._id: 1 }"
}