Updates
Updates in 2.0, are issued using a Query
instance . These update operations are executed on the server without fetching any documents across the wire.
Update operations are defined using a set of functions as defined on
UpdateOperators.
In our examples, we’ll be using the following model:
@Entity("hotels")
public class Hotel
{
@Id
private ObjectId id;
private String name;
private int stars;
@Embedded
private Address address;
List<Integer> roomNumbers = new ArrayList<Integer>();
// ... getters and setters
}
@Embedded
public class Address
{
private String street;
private String city;
private String postalCode;
private String country;
// ... getters and setters
}
set()/unset()
To change the name of the hotel, one would use something like this:
datastore
.find(Hotel.class)
.update(UpdateOperators.set("name", "Fairmont Chateau Laurier"))
.execute();
The execute()
can optionally take UpdateOptions if there are any options you might want to apply to your update statement.
Embedded documents are updated the same way. To change the name of the city in the address, one would use something like this:
datastore
.find(Hotel.class)
.update(UpdateOperators.set("address.city", "Ottawa"))
execute();
Values can also be removed from documents as shown below:
datastore
.find(Hotel.class)
.update(UpdateOperators.unset("name"))
execute();
After this update, the name of the hotel would be null
when the entity is loaded.
Multiple Updates
By default, an update operation will only update the first document matching the query.
This behavior can be modified via the optional
UpdateOptions parameter on execute()
:
datastore
.find(Hotel.class)
.inc("stars")
.execute(new UpdateOptions()
.multi(true));
Upserts
In some cases, updates are issued against a query that might not match any documents. In these cases, it’s often fine for those updates to simply pass with no effect. In other cases, it’s desirable to create an initial document matching the query parameters. Examples of this might include user high scores, e.g. In cases like this, we have the option to use an upsert:
datastore
.find(Hotel.class)
.filter(gt("stars", 100))
.update()
.execute(new UpdateOptions()
.upsert(true));
// creates { "_id" : ObjectId("4c60629d2f1200000000161d"), "stars" : 50 }
Checking results
In all this one thing we haven’t really looked at is how to verify the results of an update.
The execute()
method returns an instance of
com.mongodb.client.result.UpdateResult
.
Using this class, you can get specific numbers from the update operation as well as any generated ID as the result of an upsert.
Returning the updated entity
There are times when a document needs to be updated and also fetched from the database.
In the server documentation, this is referred to as findAndModify.
In Morphia, this functionality is exposed through the
Query#modify()
method. With this method, you can choose to return the updated entity in either the
state before or after the update. The default is to return the entity in the before state.
This can be changed by passing in a ModifyOptions
reference to the operation:
datastore
.find(Hotel.class)
.modify(UpdateOperators.set("address.city", "Ottawa"))
execute(new ModifyOptions()
.returnDocument(ReturnDocument.AFTER));
Merges
A specialized form of an update is the merge() operation.
A common case in scenarios where multiple subsystems or applications share a database is where one part of the system might only have part of a larger entity. e.g., a
DTO might only have a subset of an entity necessary to perform the duties of that particular service.
Updating the database with the partial entities can be problematic because it forces the application developer to inspect an entity’s state and manually generate the correct set of update statements.
Morphia 2.0 introduced the merge()
method to help cover this case.
Using merge()
, a specific set of $set
updates are issued to only save fields as defined on the DTO type itself (which, of course, must be annotated with @Entity
like any other Morphia type).
This is useful but limited.
What happens to any null fields and empty Lists?
Using MapperOptions
, Morphia can be configured to not persist those fields such that they never end up in the database.
In the above scenario, however, Morphia is only issuing a $set
for the fields that should be persisted which leaves potentially outdated information in the database. (e.g., say a process removes the final item from a List in memory. merge()
would actually leave that last item in the database because an update would not be issued for that empty List.) In 2.2, a new value is added to the optional InsertOneOptions to account for this.
Setting unsetMissing to true, any property defined on an entity
that isn’t getting updated via $set
will have a $unset
operator defined.
This will result in null properties and empty Lists getting removed from documents in the database so that they will reflect the current state in memory.
Regardless of whether this value is set, merge()
will issue a find()
for that entity and will return the updated form from the database.
Without using unsetMissing()
, this is useful for merging the in memory state with what’s in the database.
With this value set, the two should be identical, of course.