Как будто вы пытаетесь получить доступ к объекту, который является null
. Рассмотрим ниже пример:
TypeA objA;
. В это время вы только что объявили этот объект, но не инициализировали или не инициализировали. И всякий раз, когда вы пытаетесь получить доступ к каким-либо свойствам или методам в нем, он будет генерировать NullPointerException
, что имеет смысл.
См. Также этот пример:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
К сожалению, (по крайней мере, для Hibernate) изменение поля @Version
вручную не сделает его еще одной «версией». То есть оптимизированная проверка параллелизма выполняется против значения версии, полученной при чтении объекта, а не в поле версии объекта при его обновлении.
, например.
Это будет работать
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
// Assume at this point of time someone else change the record in DB,
// and incrementing version in DB to 3
fooRepo.flush(); // forcing an update, then Optimistic Concurrency exception will be thrown
Однако это не сработает
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
foo.setVersion(1);
fooRepo.flush(); // forcing an update, no optimistic concurrency exception
// Coz Hibernate is "smart" enough to use the original 2 for comparison
. Есть способ обхода этого. Самый простой способ - это, вероятно, реализовать оптимистичную проверку параллельности самостоятельно. Раньше у меня была утилита для сбора данных «DTO to Model», и я поставил туда эту логику проверки версий. Другой способ - поместить логику в setVersion()
, которая вместо реальной установки выполняет проверку версии:
class User {
private int version = 0;
//.....
public void setVersion(int version) {
if (this.version != version) {
throw new YourOwnOptimisticConcurrencyException();
}
}
//.....
}
В дополнение к ответу @Adrian Shum, я хочу показать, как я решил эту проблему. Если вы хотите вручную изменить версию Entity и выполнить обновление, чтобы вызвать OptimisticConcurrencyException
, вы можете просто скопировать Entity со всем своим полем, что заставило объект покинуть свой контекст (так же, как EntityManager.detach()
). Таким образом, он ведет себя надлежащим образом.
Entity entityCopy = new Entity();
entityCopy.setId(id);
... //copy fields
entityCopy.setVersion(0L); //set invalid version
repository.saveAndFlush(entityCopy); //boom! OptimisticConcurrencyException
EDIT: собранная версия работает, только если кэш-память спящего режима не содержит объект с тем же идентификатором. Это не сработает:
Entity entityCopy = new Entity();
entityCopy.setId(repository.findOne(id).getId()); //instance loaded and cached
... //copy fields
entityCopy.setVersion(0L); //will be ignored due to cache
repository.saveAndFlush(entityCopy); //no exception thrown
Правильно ответ на часть ответа @AdrianShum.
. Поведение сравнения версий по умолчанию в основном выполняет следующие шаги:
Теперь предположим, что между шагами 1 и 3 объект был изменен другой транзакцией, поэтому его номер версии на этапе 3 не является V1. Затем, когда номер версии отличается, запрос на обновление не будет изменять какой-либо реестр, hibernate поймет это и выбросит исключение.
Вы можете просто протестировать это поведение и убедиться, что исключение выбрано напрямую, изменяя номер версии в вашей базе данных между шагами 1 и 3.
Изменить. Не знаете, какой провайдер постоянства JPA вы используете с Spring Data JPA, но для получения более подробной информации об оптимизации блокировки с JPA + Hibernate я предлагаю вам прочитать главу 10, раздел Управление одновременным доступом , книги Сохранение Java с гибернацией (спящий режим в действии)
@Version
в Entity. Вместо этого Hibernate хранит версию сущности где-то еще и использует ее для оптимизации проверки параллелизма.
– Adrian Shum
18 June 2015 в 10:35