Skip to content

@MongoId not working for nested object queries using a projection class #4557

Open
@nniesen

Description

@nniesen

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

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions