JPA и оптимистичные режимы блокировки
Я прочитал статью в блоге Oracle здесь о JPA и режимах блокировки.
Я не совсем понимаю разницу между OPTIMISTIC
а также OPTIMISTIC_FORCE_INCREMENT
типы режимов блокировки.
OPTIMISTIC
Режим:
Когда пользователь блокирует объект в этом режиме, выполняется проверка объекта поля версии (@version
) в начале транзакции и проверка поля версии также выполняется в конце транзакции. Если версии разные, транзакция откатывается.
OPTIMISTIC_FORCE_INCREMENT
Режим:
Когда пользователь выбирает этот режим, он должен сбросить () состояние EntityManager в базу данных, чтобы вручную увеличить поле версии. Таким образом, все другие оптимистичные транзакции будут признаны недействительными (откат). Проверка версии также выполняется в конце транзакции для фиксации или отката транзакции.
Кажется ясно, но когда я должен использовать OPTIMISTIC
против OPTIMISTIC_FORCE_INCREMENT
режимы? Единственные критерии, которые я вижу, это применять OPTIMISTIC_FORCE_INCREMENT
режим, когда я хочу, чтобы транзакция имела приоритет над остальными, потому что выбор этого режима откатит все остальные запущенные транзакции (если я хорошо понимаю механизм).
Есть ли другая причина, чтобы выбрать этот режим, а не OPTIMISTIC
Режим?
Спасибо
3 ответа
Обычно вы никогда не использовали бы API lock() для оптимистичной блокировки. JPA автоматически проверит любые столбцы версий при любом обновлении или удалении.
Единственная цель API lock() для оптимистичной блокировки - это когда ваше обновление зависит от другого объекта, который не был изменен / обновлен. Это позволяет вашей транзакции все еще терпеть неудачу, если другой объект изменяется.
Когда это сделать, зависит от приложения и варианта использования. OPTIMISTIC гарантирует, что другой объект не был обновлен во время вашей фиксации. OPTIMISTIC_FORCE_INCREMENT будет гарантировать, что другой объект не был обновлен, и увеличит его версию при фиксации.
Оптимистическая блокировка всегда проверяется при фиксации, и нет гарантии успеха до фиксации. Вы можете использовать flush() для принудительной блокировки базы данных раньше времени или вызвать более раннюю ошибку.
Не пугайся этого длинного ответа. Эта тема не простая.
По умолчанию JPA устанавливает уровень изоляции " зафиксировано чтение", если не указана блокировка (такое же поведение, как при использовании LockModeType.NONE
).
Чтение совершенных требует отсутствия явления Грязное чтение. Просто T1 может видеть изменения, сделанные T2 только после фиксации T2.
Использование оптимистической блокировки в JPA повышает уровень изоляции до повторяющихся операций чтения.
Если T1 читает некоторые данные в начале и в конце транзакции, Repetable читает гарантирует, что T1 видит те же данные, даже если T2 изменил данные и зафиксировал их в середине T1.
И тут начинается сложная часть. JPA достигает Repetable reads самым простым способом: предотвращая феномен Repetable read. JPA не достаточно сложен, чтобы делать снимки ваших чтений. Он просто предотвращает повторное чтение, вызывая исключение (если данные изменились с первого чтения).
Вы можете выбрать один из двух вариантов оптимистичной блокировки:
LockModeType.OPTIMISTIC
(LockModeType.READ
в JPA 1.0)LockModeType.OPTIMISTIC_FORCE_INCREMENT
(LockModeType.WRITE
в JPA 1.0)
Какая разница между этими двумя?
Позвольте мне проиллюстрировать это примерами Person
юридическое лицо.
@Entity
public class Person {
@Id int id;
@Version int version;
String name;
String label;
@OneToMany(mappedBy = "person", fetch = FetchType.EAGER)
List<Car> cars;
// getters & setters
}
Теперь предположим, что в базе данных хранится один человек по имени Джон. Мы читаем этого человека в T1, но меняем его имя на Mike во второй транзакции T2.
Без какой-либо блокировки
Person person1 = em1.find(Person.class, id, LockModeType.NONE); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(em1.find(Person.class, id).getName()); // prints "John" - entity is already in Persistence cache
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // prints 0 - ups! don't know about any John (Non-repetable read)
Оптимистичная блокировка чтения
Person person1 = em1.find(Person.class, id, LockModeType.OPTIMISTIC); //T1 reads Person("John")
Person person2 = em2.find(Person.class, id); //T2 reads Person("John")
person2.setName("Mike"); //Changing name to "Mike" within T2
em2.getTransaction().commit(); // T2 commits
System.out.println(
em1.createQuery("SELECT count(p) From Person p where p.name='John'")
.getSingleResult()); // OptimisticLockException - The object [Person@2ac6f054] cannot be updated because it has changed or been deleted since it was last read.
LockModeType.OPTIMISTIC_FORCE_INCREMENT
используется, когда изменение вносится в другую сущность (возможно, в не принадлежащие ей отношения), и мы хотим сохранить целостность. Позвольте мне проиллюстрировать, как Джон приобретает новую машину.Оптимистичная блокировка чтения
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. We'll end up with wrong info about John.
if (john1.getCars().size() > 0) {
john1.setLabel("John has a car");
} else {
john1.setLabel("John doesn't have a car");
}
em1.flush();
Оптимистичная блокировка записи
Person john1 = em1.find(Person.class, id); //T1 reads Person("John")
Person john2 = em2.find(Person.class, id, LockModeType.OPTIMISTIC_FORCE_INCREMENT); //T2 reads Person("John")
//John gets a mercedes
Car mercedes = new Car();
mercedes.setPerson(john2);
em2.persist(mercedes);
john2.getCars().add(mercedes);
em2.getTransaction().commit(); // T2 commits
//T1 doesn't know about John's new car. john1 in stale state. That's ok though because proper locking won't let us save wrong information about John.
if (john1.getCars().size() > 0) {
john1.setLabel("John has a car");
} else {
john1.setLabel("John doesn't have a car");
}
em1.flush(); // OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
Хотя в спецификации JPA есть следующее замечание, Hibernate и EclipseLink ведут себя хорошо и не используют его.
Для версионных объектов для реализации допустимо использовать LockMode- Type.OPTIMISTIC_FORCE_INCREMENT, где запрашивался LockModeType.OPTIMISTIC, но не наоборот.
Как объясняется в этом посте, LockModeType.OPTIMICTIC
страдает от проблем с проверкой действий, поэтому лучше связать их с PESSIMISTIC_READ или PESSIMISTIC_WRITE.
С другой стороны, LockModeType.OPTIMICTIC_FORCE_INCREMENT
не страдает от какой-либо проблемы несоответствия данных, и вы, как правило, используете его для управления версией родительской сущности всякий раз, когда изменяется дочерняя сущность.
Проверьте эту статью для более подробной информации о том, как вы можете использовать LockModeType.OPTIMICTIC_FORCE_INCREMENT
или же LockModeType.PESSIMISTIC_FORCE_INCREMENT
так что версия родительской сущности учитывает также и дочернюю сущность.