References
Morphia supports two styles of defining references: the @Reference
annotation and the experimental
MorphiaReference
.
The annotation based approach is discussed
here.
This guide will cover the wrapper based approach.
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 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, Map`s 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 |