References
MongoDB tries to encourage self-contained documents so that data can be fetched much more efficiently via single reads from the database. However not every data model or application can adhere to this philosophy in its entirety. As such, MongoDB supports database references.Morphia supports two styles of defining references:
-
the
@Reference
annotation -
an experimental wrapper type
MorphiaReference
.
Using the annotation
The annotation based approach is the traditional approach.
The @Reference
can be applied to any property whose type is a mappable Entity type.
When this is done, instead of storing that objects information as a subdocument in the larger document sent to the database, a reference will be stored instead which points to where the document is actually stored.
The referenced types are stored according to the mapping information configured by their
@Entity
annotations.
It can be helpful to think of references as a foreign key like you would find in a relational database. It’s important to note, however, that MongoDB does not enforce any notion of referential integrity with these keys. Those remote documents may or may not actually exist and you will have to tailor your application (and configure Morphia as we’ll see in a moment) to deal with those missing documents. |
The annotation provides a number of options to help tailor Morphia’s behavior to your liking.
-
idOnly
— defaults to false Whentrue
, only the value of the ID field is stored in the enclosing documents. If the referenced entity type has no subclasses, setting this can help save disk space and network traffic. If there are subtypes, this setting may be overridden automatically so that type information can properly stored. -
ignoreMissing
defaults to false Whentrue
, references that can not be loaded are simply ignored. Otherwise an exception is thrown whenever the reference load is attempted. -
lazy
defaults to false Whentrue
the referenced entity will not be fetched until the property is explicitly referenced. Otherwise the referenced entity (or entities) are loaded as part of the query load cycle of the enclosing entity.
A String
may be passed to the annotation to define the document field name to be stored in the database.
Using the wrapper type
This API is experimental. Its implementation and API subject to change based on user feedback. However, users are encouraged to experiment with the API and provide as much feedback as possible both positive and negative as this will likely be the approach used going forward. |
An alternative to the traditional annotation-based approach is the
MorphiaReference
wrapper.
This type can not be instantiated directly.
Instead a wrap()
method is provided that will construct the proper type and track the necessary state.
Currently, four different types of values are supported by wrap()
: a reference to a single entity, a List of references to entities, a Set, and a Map. wrap()
will determine how best to handle the type passed and create the appropriate structures internally.
This is how this type might be used in practice:
private class Author {
@Id
private ObjectId id;
private String name;
private MorphiaReference<List<Book>> list;
private MorphiaReference<Set<Book>> set;
private MorphiaReference<Map<String, Book>> map;
public Author() { }
public List<Book> getList() {
return list.get();
}
public void setList( List<Book> list) {
this.list = MorphiaReference.wrap(list);
}
public Set<Book> getSet() {
return set.get();
}
public void setSet( Set<Book> set) {
this.set = MorphiaReference.wrap(set);
}
public Map<String, Book> getMap() {
return map.get();
}
public void setMap( Map<String, Book> map) {
this.map = MorphiaReference.wrap(map);
}
}
private class Book {
@Id
private ObjectId id;
private String name;
private MorphiaReference<Author> author;
public Book() { }
public Author getAuthor() {
return author.get();
}
public void setAuthor( Author author) {
this.author = MorphiaReference.wrap(author);
}
}
As you can see we have 3 different references from Author
to Book
and one in the opposite direction.
It would also be good to note that the public API of those two classes don’t expose the MorphiaReference
externally.
This is, of course, a stylistic choice but is the encouraged approach as it avoids leaking out implementation and mapping details outside of your model.
setList()
accepts a List<Book>
and stores them as references to Book
instances stored in the collection as defined by the mapping metadata for Book
.
Because these references point to the mapped collection name for the type, we can get away with storing only the _id
fields for each book.
This gives us data in the database that looks like this:
> db.Author.find().pretty()
{
"_id" : ObjectId("5c3e99276a44c77dfc1b5dbd"),
"className" : "dev.morphia.mapping.experimental.MorphiaReferenceTest$Author",
"name" : "Jane Austen",
"list" : [
ObjectId("5c3e99276a44c77dfc1b5dbe"),
ObjectId("5c3e99276a44c77dfc1b5dbf"),
ObjectId("5c3e99276a44c77dfc1b5dc0"),
ObjectId("5c3e99276a44c77dfc1b5dc1"),
ObjectId("5c3e99276a44c77dfc1b5dc2")
]
}
As you can see, we only need to store the ID values because the collection is already known elsewhere.
However, sometimes we need to refer to documents stored in different collections.
For example, if the generic type of the reference is a parent interface or class, we sometimes need to store extra information.
For these cases, we store the references as full DBRef
instances so we can track the appropriate collection for each reference.
Using this version, we get data in the database that looks like this:
> db.jane.find().pretty()
{
"_id" : ObjectId("5c3e99c06a44c77e5a9b5701"),
"className" : "dev.morphia.mapping.experimental.MorphiaReferenceTest$Author",
"name" : "Jane Austen",
"list" : [
DBRef("books", ObjectId("5c3e99c06a44c77e5a9b5702")),
DBRef("books", ObjectId("5c3e99c16a44c77e5a9b5703")),
DBRef("books", ObjectId("5c3e99c16a44c77e5a9b5704")),
DBRef("books", ObjectId("5c3e99c16a44c77e5a9b5705")),
DBRef("books", ObjectId("5c3e99c16a44c77e5a9b5706"))
]
}
In both cases, we have a document field called list
but as you can see in the second case, we’re not storing just the _id
values but
DBRef
instances storing both the collection name, "books" in this case, and ObjectId
values from the Books.
This lets the wrapper properly reconstitute these references when you’re ready to use them.
Before we go too much further, it’s important to point that, regardless of the type of the references, they are fetched lazily.
So if you have multiple fields with referenced entities, they will not be fetched until you call |
A Set
of references will look no different in the database than the List
does.
However, a Map
of references are slightly more complicated.
A Map
might look something like this:
> db.Author.find().pretty()
{
"_id" : ObjectId("5c3e9cad6a44c77fa8f38f58"),
"className" : "dev.morphia.mapping.experimental.MorphiaReferenceTest$Author",
"name" : "Jane Austen",
"map" : {
"Sense and Sensibility " : ObjectId("5c3e9cad6a44c77fa8f38f59"),
"Pride and Prejudice" : ObjectId("5c3e9cad6a44c77fa8f38f5a"),
"Mansfield Park" : ObjectId("5c3e9cad6a44c77fa8f38f5b"),
"Emma" : ObjectId("5c3e9cad6a44c77fa8f38f5c"),
"Northanger Abbey" : ObjectId("5c3e9cad6a44c77fa8f38f5d")
}
}
References to single entities will follow the same pattern with regards to the _id
values vs DBRef
entries.
Currently there is no support for configuring the |