Single-Collection Designs in MongoDB with Spring Data (Part 2)
Rate this tutorial
In , we discussed single-collection design patterns in MongoDB and how they can be used to avoid the need for computationally expensive joins across collections. In this second part of the series, we will provide examples of how the single-collection pattern can be utilized in Java applications using and, in particular, how documents representing different classes but residing in the same collection can be accessed.
Whilst official, native idiomatic interfaces for MongoDB are available for 12 different programming languages, with community-provided interfaces available for many more, many of our customers have significant existing investment and knowledge developing Java applications using Spring Data. A common question we are asked is how can polymorphic single-collection documents be accessed using Spring Data MongoDB?
In the next few steps, I will show you how the Spring Data template model can be used to map airline, aircraft, and ADSB position report documents in a single collection named aerodata, to corresponding POJOs in a Spring application.
The code examples that follow were created using the Netbeans IDE, but any IDE supporting Java IDE, including Eclipse and IntelliJ IDEA, can be used.
Generate your template project, unpack it, and open it in your IDE:
Add a package to your project to store the POJO, repository class, and interface definitions. (In my project, I created a package called (com.mongodb.devrel.gcr.aerodata). For our demo, we will add four POJOs — AeroData, Airline, Aircraft, and ADSBRecord — to represent our data, with four corresponding repository interface definitions. AeroData will be an abstract base class from which the other POJOs will extend:
We’ll also add a GeoPoint class to hold location information within the ADSBRecord objects:
Note the annotations used in the four main POJO classes. We’ve used the “@Document” annotation to specify the MongoDB collection into which data for each class should be saved. In each case, we’ve specified the “aeroData” collection. In the Airline, Aircraft, and ADSBRecord classes, we’ve also used the “@TypeAlias” annotation. Spring Data will automatically add a “_class” field to each of our documents containing the Java class name of the originating object. The TypeAlias annotation allows us to override the value saved in this field and can be useful early in a project’s development if it’s suspected the class type may change. Finally, in the AeroData abstract class, we’ve used the “@id” annotation to specify the field Spring Data will use in the MongoDB _id field of our documents.
Note that having unencrypted user credentials in a properties file is obviously not best practice from a security standpoint and this approach should only be used for testing and educational purposes. For more details on options for connecting to MongoDB securely, including the use of keystores and cloud identity mechanisms, refer to the .
With our connection details in place, we can now update the main application entry class. Because we are not using a view or controller, we’ll set the application up as a CommandLineRunner to view output on the command line:
Spring Boot takes care of a lot of details in the background for us, including establishing a connection to MongoDB and autowiring our repository classes. On running the application, we are:
- Using the save method on the Airline, Aircraft, and ADSBRecord repositories respectively to add an airline, three aircraft, and three ADSB position report documents to our collection.
- Using the findAll and findById methods on the Airline, Aircraft, and ADSBRecord repositories respectively to retrieve, in turn, all airline documents, a specific airline document, all aircraft documents, a specific aircraft document, all ADSB position report documents, and a specific ADSB position report document.
If everything is configured correctly, we should see the following output on the command line:
As you can see, our data has been successfully added to the MongoDB collection, and we are able to retrieve the data. However, there is a problem. The findAll methods of each of the repository objects are returning a result for every document in our collection, not just the documents of the class type associated with each repository. As a result, we are seeing seven documents being returned for each record type — airline, aircraft, and ADSB — when we would expect to see only one airline, three aircraft, and three ADSB position reports. Note this issue is common across all the “All” repository methods — findAll, deleteAll, and notifyAll. A call to the deleteAll method on the airline repository would result in all documents in the collection being deleted, not just airline documents.
To address this, we have two options: We could override the standard Spring Boot repository findAll (and deleteAll/notifyAll) methods to factor in the class associated with each calling repository class, or we could extend the repository interface definitions to include methods to specifically retrieve only documents of the corresponding class. In our exercise, we’ll concentrate on the later approach by updating our repository interface definitions:
In each interface, we’ve added two new function definitions — one to return all documents of the relevant type, and one to allow documents to be returned when searching by ICAO address. Using the @Query annotation, we are able to format the queries as needed.
With our function definitions in place, we can now update the main application class:
Note that as well as the revised search calls, we also added a call to deleteAll on the airline repository to remove data added by prior runs of the application.
With the updates in place, when we run the application, we should now see the expected output:
In this two-part post, we have seen how polymorphic single-collection designs in MongoDB can provide all the query flexibility of normalized relational designs, whilst simultaneously avoiding anti-patterns such as unbounded arrays and unnecessary joins. This makes the resulting collections highly performant from a search standpoint and amenable to horizontal scaling. We have also shown how we can work with these designs using Spring Data MongoDB.