GAE обновляет разные поля одной и той же сущности
UserA и UserB изменяют objectA.filedA objectA.filedB соответственно и одновременно. Поскольку они не меняют одно и то же поле, можно подумать, что нет совпадений. Это правда? или реализация pm.makePersistnace() фактически перекрывает весь объект... полезно знать...
2 ответа
Это то, что вы предполагаете, происходит?
- Алиса возвращает объект. { a = 1, b = "Foo" }
- Боб забирает объект. { a = 1, b = "Foo" }
- Алиса изменяет объект, изменяя b. { a = 1, b = "Бар"}
- Боб изменяет объект, изменяя. { a = 2, b = "Foo" }
- Алиса сохраняет свою копию объекта. {a = 1, b = "Бар"}
- Боб сохраняет свою копию объекта. { a = 2, b = "Foo" }
Копия объекта Бобом перезапишет копию в хранилище данных, потому что он сохраняет весь свой объект, а не только набор измененных полей. Или, в общем, какой бы из них не сохранился в последний раз, весь их объект сохраняется в хранилище данных.
Это можно исправить, выполнив каждую из операций get-set-and-persist в транзакции. Транзакции App Engine не блокируют весь объект от извлечения или изменения локально, они просто предотвращают сохранение других пользователей. Так:
- Алиса получает объект в транзакции. { a = 1, b = "Foo" }
- Боб получает объект в транзакции. { a = 1, b = "Foo" }
- Алиса изменяет объект, изменяя b. { a = 1, b = "Бар"}
- Боб изменяет объект, изменяя. { a = 2, b = "Foo" }
- Алиса пытается сохранить объект, но не может, потому что Боб открыл его в транзакции. Будет сгенерировано исключение, которое Алиса поймает, завершив ее перевод и повторив попытку...
- Боб сохраняет объект без проблем, потому что Алиса завершила свою транзакцию {a = 2, b = "Foo"}
- Алиса повторяет свою транзакцию, получая снова. { a = 2, b = "Foo" }
- Алиса изменяет объект, изменяя b. { a = 2, b = "Бар"}
- Алиса сохраняет объект и работает, потому что ни у кого больше нет открытой транзакции. {a = 2, b = "Бар"}
Я не совсем уверен, какой пользователь получит исключение, но если они захотят повторить попытку, увидев его, они оба смогут вносить свои изменения в объект и сохранять их, в конце концов.
Это называется оптимистической блокировкой.
Спасибо за Ваш ответ. Жаль, что реализация makePersistence() предназначена для записи целого объекта в хранилище данных, а не только в поля, которые были изменены. Этот факт фактически заставляет ЛЮБОЕ обновление общего объекта в GAE использовать транзакцию как правило. Более того - в таких случаях вы должны реализовать "механизм повторных попыток", поскольку в транзакции может возникнуть исключение.
Итак... обновление любого общего объекта в GAE должно ВСЕГДА иметь эти дополнения:
- сделать это в рамках транзакции
- реализовать механизм повтора
Большинство примеров Google на их сайте фактически не принимают это во внимание. Как будто они предполагают, что большинство приложений не будут использовать общие объекты
Например ( 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();
}
}
ИЛИ ЖЕ:
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);
}
}