1

I am using Spring boot with MongoDB to define a OneToMany relationship between the Class Building and Zone (a Building contains many zones and each zone has an idBuilding field to specify to which building it belongs). In a typical MySQL-based syntax there's no problem (i define it using unidirectional OneToMany relationship between Building and Zone, but with mongoDB syntax i am having some trouble to do it. My question is how to define such unidirectional OneToMany relationship for MongoDB accounting for the CASCADE option (with or without using @DBRef)? and how to query them using the @Query annotation.

Here's my try for mongoDB for the relationships (i'm using the embbeded technique) :

    @Document("Building")
    public class Building {
     private Zone[] zones;
     ...
    }

    @Document("Zone")
    public class Zone {
     private Building building;
     ...
    }

AND for the query :

@Repository
public interface ZoneRepository extends  MongoRepository <Zone, Long> {


    @Query(value = "{'building.id': ?0}")
    List<Zone> queryZoneByBuildingId(@Param("id") Long id);
}

Please suggest modification to this setup.

lopmkipm
  • 67
  • 2
  • 10

1 Answers1

4

I'd advise your read this excellent MongoDb documentation article that gives recommendations about how you should model your relationships: https://docs.mongodb.com/manual/reference/database-references

There are 3 options:

  1. Embedding the related entity
  2. Using manual references
  3. Using dbref

Embedding:

For many use cases in MongoDB, the denormalized data model where related data is stored within a single document will be optimal

Manual references vs DBRef

Unless you have a compelling reason to use DBRefs, use manual references instead.

If you can use an embedded document, then just embed the related entity into the other (@Document("Zone") is not needed in your case):

    @Document("Building")
    public class Building {
        private Zone[] zones;
        ...
    }


    public class Zone {
        private String whatever;
        ...
    }

Then in order to get the zones by buildingId, just get the building from the repository and return its nested collection:

zoneRepository.findById(buildingId).getZones()

If as stated in the comments, you have to separate them in different collections I'd suggest you denormalize the information:

    @Document("Building")
    public class Building {
        private Zone[] zones;
        ...
    }


    @Document("Building")
    public class Zone {
        private String buildindId;
        ...
    }
@Repository
public interface ZoneRepository extends  MongoRepository <Zone, Long> {


    @Query(value = "{'buildingId': ?0}")
    List<Zone> queryZoneByBuildingId(@Param("id") Long id);
}

Regarding cascades: MongoDb doesn't support cascading deletes (see: mongoDB alternatives for foreign key constraints). The easiest way to deal with them is using embedded documents (when you delete the parent, its nested collection will be deleted).

In your case, however you would have to delete them manually when you delete the parent entity:

public void deleteBuilding(String buildingId) {

    buildingRepository.deleteById(buildingId);
    //delete zones with buildingId 

}
codependent
  • 23,193
  • 31
  • 166
  • 308
  • Thank you but i'd rather not embed classes that way – lopmkipm Jun 14 '20 at 11:36
  • What's the reason why you consider embedding is not a good fit for your application? – codependent Jun 14 '20 at 11:47
  • Because i have to follow instructions from the application's design plan. Plus i need to underline the Cascade option in the code. – lopmkipm Jun 14 '20 at 11:52
  • Does the building have to be created first in order to assign to a Zone a building Id ? – lopmkipm Jun 14 '20 at 12:10
  • I guess it does, unless your business allows zones wihout an assigned building – codependent Jun 14 '20 at 12:21
  • How could `DBRef` play a role to achieve a better linkage between the two classes here ? in particular for the OneToMany relationship ? PS : in your solution you can add an idBuilding to a zone without the building being already created. – lopmkipm Jun 14 '20 at 12:21
  • In order to better understand DBRef usage have a look at his Baeldung article: https://www.baeldung.com/cascading-with-dbref-and-lifecycle-events-in-spring-data-mongodb **NOTICE:** Note that the mapping framework doesn't handle cascading operations. So – for instance – if we trigger a save on a parent, the child won't be saved automatically – we'll need to explicitly trigger the save on the child if we want to save it as well. – codependent Jun 14 '20 at 12:33
  • Thanks for the link & the explanation, does the embedding technique kinda palliates to this problem of relatioships & cascade option ? isn't this a big drawback compared to MySQL (the fact that it doesnt handle cascade and relationships)? – lopmkipm Jun 14 '20 at 12:48
  • Embedding should be considered first in MongoDb applications since it palliates the cascading problem. – codependent Jun 14 '20 at 12:52
  • Just to add that in your solution with embedding Zone in Building document the Class Zone should be private because only one Public class is allowed per .java file – lopmkipm Jun 14 '20 at 18:29
  • They are meant to be put in separate files – codependent Jun 14 '20 at 19:53
  • Oh ! then i can't see the structure clearly, with what file names you do that seperately?? with one document("Booking") annotation for both classes/files? would they be declared with @entity too ? – lopmkipm Jun 14 '20 at 20:07
  • No @Entity, that’s for JPA. You would have Building.java and Zone.java, each with its corresponding Document annotation. – codependent Jun 14 '20 at 20:32
  • I just tried that if i remove building then zone is removed, but then if i add a zone then it counts as a building (it shows in the getAll result list of building) ! anyway what about if i have also Floor subcomposing Zone, do i have to define Floor as document("Zone") ? and zone stays document("Building") ? thanks alot ! – lopmkipm Jun 14 '20 at 20:45