Search code examples
springmongodbspring-dataspring-data-document

Spring Data MongoDB: Accessing and updating sub documents


First experiments with Spring Data and MongoDB were great. Now I've got the following structure (simplified):

public class Letter {
  @Id
  private String id;
  private List<Section> sections;
}

public class Section {
  private String id;
  private String content;
}

Loading and saving entire Letter objects/documents works like a charm. (I use ObjectId to generate unique IDs for the Section.id field.)

Letter letter1 = mongoTemplate.findById(id, Letter.class)
mongoTemplate.insert(letter2);
mongoTemplate.save(letter3);

As documents are big (200K) and sometimes only sub-parts are needed by the application: Is there a possibility to query for a sub-document (section), modify and save it? I'd like to implement a method like

Section s = findLetterSection(letterId, sectionId);
s.setText("blubb");
replaceLetterSection(letterId, sectionId, s);

And of course methods like:

addLetterSection(letterId, s); // add after last section
insertLetterSection(letterId, sectionId, s); // insert before given section
deleteLetterSection(letterId, sectionId); // delete given section

I see that the last three methods are somewhat "strange", i.e. loading the entire document, modifying the collection and saving it again may be the better approach from an object-oriented point of view; but the first use case ("navigating" to a sub-document/sub-object and working in the scope of this object) seems natural.

I think MongoDB can update sub-documents, but can SpringData be used for object mapping? Thanks for any pointers.


Solution

  • I figured out the following approach for slicing and loading only one subobject. Does it seem ok? I am aware of problems with concurrent modifications.

    Query query1 = Query.query(Criteria.where("_id").is(instance));
    query1.fields().include("sections._id");
    LetterInstance letter1 = mongoTemplate.findOne(query1, LetterInstance.class); 
    LetterSection emptySection = letter1.findSectionById(sectionId);
    int index = letter1.getSections().indexOf(emptySection);
    
    Query query2 = Query.query(Criteria.where("_id").is(instance));
    query2.fields().include("sections").slice("sections", index, 1);
    LetterInstance letter2 = mongoTemplate.findOne(query2, LetterInstance.class);
    LetterSection section = letter2.getSections().get(0);
    

    This is an alternative solution loading all sections, but omitting the other (large) fields.

    Query query = Query.query(Criteria.where("_id").is(instance));
    query.fields().include("sections");
    LetterInstance letter = mongoTemplate.findOne(query, LetterInstance.class); 
    LetterSection section = letter.findSectionById(sectionId);
    

    This is the code I use for storing only a single collection element:

    MongoConverter converter = mongoTemplate.getConverter();
    DBObject newSectionRec = (DBObject)converter.convertToMongoType(newSection);
    
    Query query = Query.query(Criteria.where("_id").is(instance).and("sections._id").is(new ObjectId(newSection.getSectionId())));
    Update update = new Update().set("sections.$", newSectionRec);
    mongoTemplate.updateFirst(query, update, LetterInstance.class);
    

    It is nice to see how Spring Data can be used with "partial results" from MongoDB.

    Any comments highly appreciated!