GAE update different fields of the same entity
UserA and UserB are changing objectA.filedA objectA.filedB respectively and at the same time. Because they are not changing the same field one might think that there are no overlaps. Is that true? or the implementation of pm.makePersistnace() ac开发者_如何学Pythontually override the whole object... good to know...
Is this what you're envisioning happening?
- Alice retrieves the object. { a = 1, b = "Foo" }
- Bob retrieves the object. { a = 1, b = "Foo" }
- Alice modifies the object, by changing b. { a = 1, b = "Bar" }
- Bob modifies the object, by changing a. { a = 2, b = "Foo" }
- Alice persists her copy of the object. { a = 1, b = "Bar" }
- Bob persists his copy of the object. { a = 2, b = "Foo" }
Bob's copy of the object will overwrite the copy in the datastore, because he's persisting his whole object, not just his set of changed fields. Or, in general, whichever of them persists last has their whole object persisted in the datastore.
You could fix this by running each of their get-set-and-persist operations in a transaction. App Engine transactions don't lock the whole object from being retrieved or modified locally, they just prevent other users from persisting. So:
- Alice retrieves the object in a transaction. { a = 1, b = "Foo" }
- Bob retrieves the object in a transaction. { a = 1, b = "Foo" }
- Alice modifies the object, by changing b. { a = 1, b = "Bar" }
- Bob modifies the object, by changing a. { a = 2, b = "Foo" }
- Alice tries to persist the object, but can't, because Bob has it open in a transaction. An exception will be thrown, which Alice will catch by ending her transcation and retrying...
- Bob persists the object, with no problems, because Alice has finished his transaction { a = 2, b = "Foo" }
- Alice retries her transaction by retrieving again. { a = 2, b = "Foo" }
- Alice modifies the object, by changing b. { a = 2, b = "Bar" }
- Alice persists the object, and it works because nobody else has a transaction open. { a = 2, b = "Bar" }
I'm not absolutely sure which user will get the exception, but as long as they're willing to retry when they see it, they'll both be able to make their changes to the object and persist them, eventually.
This is called Optimistic Locking.
Thanks for your answer. Pity that the makePersistence() implementation is to write the WHOLE object to the datastore and not only to the fields that were changed. This fact is actually forcing ANY shared object update in GAE to use a transaction as a rule. Further more - in such cases you must implement the "retry mechanism" as an exception in the transaction may occur.
So... updating any shared object in GAE should ALWAYS have these extras:
- do it within a transaction
- implement a retry mechanism
Most of Google's examples in their site are actually not taking that into account. As if they're assuming that most apps won't used shared objects
For example (http://code.google.com/appengine/docs/java/datastore/creatinggettinganddeletingdata.html):
public void updateEmployeeTitle(User user, String newTitle) {
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
Employee e = pm.getObjectById(Employee.class, user.getEmail());
if (titleChangeIsAuthorized(e, newTitle) {
e.setTitle(newTitle);
} else {
throw new UnauthorizedTitleChangeException(e, newTitle);
}
} finally {
pm.close();
}
}
OR:
public void updateEmployeeTitle(Employee e, String newTitle) {
if (titleChangeIsAuthorized(e, newTitle) {
e.setTitle(newTitle);
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.makePersistent(e);
} finally {
pm.close();
}
} else {
throw new UnauthorizedTitleChangeException(e, newTitle);
}
}
精彩评论