5

I have been working in Spring Boot with the Spring Data MongoDB project and I am seeing behavior I am not clear on. I understand that the id field will go to _id in the Mongo repository per http://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping.conventions.id-field. My problem is that it also seems to be happening for child entities which does not seem correct.

For example I have these classes (leaving out setters and getters for brevity) :

public class MessageBuild {
    @Id
    private String id;

    private String name;
    private TopLevelMessage.MessageType messageType;
    private TopLevelMessage message;
}

public interface TopLevelMessage {
    public enum MessageType {
        MapData
    }
}

public class MapData implements TopLevelMessage {
    private String layerType;
    private Vector<Intersection> intersections;
    private Vector<RoadSegment> roadSegments;
}    

public class RoadSegment {
    private int id;
    private String name;
    private Double laneWidth;
}

and I create an object graph using this I use the appropriate MongoRepository class to save I end up with an example document like this (with _class left out):

{
    "_id" : ObjectId("57c0c05568a6c4941830a626"),
    "_class" : "com.etranssystems.coreobjects.persistable.MessageBuild",
    "name" : "TestMessage",
    "messageType" : "MapData",
    "message" : {
        "layerType" : "IntersectionData",
        "roadSegments" : [ 
            {
                "_id" : 2001,
                "name" : "Road Segment 1",
                "laneWidth" : 3.3
            }
        ]
    }
}

In this case a child object with a field named id has its mapping converted to _id in the MongoDB repository. Not the end of the world although not expected. The biggest problem is now that this is exposed by REST MVC the _id fields are not returned from a query. I have tried to set the exposeIdsFor in my RepositoryRestConfigurerAdapter for this class and it exposes the id for the top level document but not the child ones.

So circling around the 2 questions/issues I have are:

  • Why are child object fields mapped to _id? My understanding is that this should only happen on the top level since things underneath are not really documents in their own right.
  • Shouldn't the configuration to expose id fields work for child objects in a document if it is mapping the field names?
Rob Baily
  • 2,770
  • 17
  • 26

1 Answers1

3

Am I wrong to think that RoadSegment does not contain a getId() ? From Spring's documentation:

A property or field without an annotation but named id will be mapped to the _id field.

I believe Spring Data does this even to nested classes, when it finds an id field. You may either add a getId(), so that the field is named id or annotate it with @Field:

public class RoadSegment {
    @Field("id")
    private int id;

    private String name;
    private Double laneWidth;
}

I agree this automatic conversion of id/_id should only be done at the top level in my opinion.

However, the way Spring Data Mongo conversion is coded, all java ojects go through the exact same code to be converted into json (both top and nested objects):

public class MappingMongoConverter {
...
    protected void writeInternal(Object obj, final DBObject dbo, MongoPersistentEntity<?> entity) {
        ...
        if (!dbo.containsField("_id") && null != idProperty) {
        try {
            Object id = accessor.getProperty(idProperty);
                dbo.put("_id", idMapper.convertId(id));
            } catch (ConversionException ignored) {}
        }

        ...
        if (!conversions.isSimpleType(propertyObj.getClass())) {
            // The following line recursively calls writeInternal with the nested object
            writePropertyInternal(propertyObj, dbo, prop); 
        } else {
            writeSimpleInternal(propertyObj, dbo, prop);
        }
}

writeInternal is called on the top level object, and then recalled recursively for each subobjects (aka SimpleTypes). So they both go through the same logic of adding _id.

Perhaps this is how we should read Spring's documentation:

  • Mongo's restrictions on Mongo Documents:

MongoDB requires that you have an _id field for all documents. If you don’t provide one the driver will assign a ObjectId with a generated value.

  • Spring Data's restrictions on java classes:

If no field or property specified above is present in the Java class then an implicit _id file will be generated by the driver but not mapped to a property or field of the Java class.

alexbt
  • 16,415
  • 6
  • 78
  • 87
  • 1
    To me this only makes sense on the top level document. Why would child entities that are just some part of the document have their values translated? – Rob Baily Aug 30 '16 at 04:39
  • 1
    Also the documentation says "MongoDB requires that you have an _id field for all documents" and the child entities are NOT documents. They are part of the document that is inserted. – Rob Baily Aug 30 '16 at 04:40
  • 1
    You downvoted me (no hard feelings, I agree I didn't answer as to 'why'), but for the record, I agree it only makes sense at the top level. This could very well be a bug ? – alexbt Aug 30 '16 at 10:29
  • Update the answer, it does not provide an explanation as to why this is a good behavior. Just why it is happening. – alexbt Aug 30 '16 at 10:47
  • Yes it seems like a bug and I was really hoping that someone from the Spring Data team would chime in and add some insight into why this behavior makes sense. I guess I had a pretty good idea that this was the behavior based on what I was seeing. Sorry for the downvote, I guess I was cranky because it was not getting to the bottom of the issue. Any ideas on why the second part doesn't work? It is odd that the ids are hidden for child entities as well but the expose setting does not change it. – Rob Baily Aug 30 '16 at 13:36