JPA и оптимизированные режимы блокировки
Я прочитал статью о блоге Oracle здесь о режимах JPA и блокировки.
Я не очень хорошо понимаю разницу между типами режима блокировки OPTIMISTIC
и OPTIMISTIC_FORCE_INCREMENT
.
OPTIMISTIC
:
![enter image description here]()
Когда пользователь блокирует объект в этом режиме, в начале транзакции выполняется проверка объекта объекта версии (@version
), а в конце транзакции также выполняется проверка поля версии. Если версии отличаются друг от друга, транзакция возвращается.
OPTIMISTIC_FORCE_INCREMENT
:
![enter image description here]()
Когда пользователь выбирает этот режим, ему нужно сбросить() состояние EntityManager в базу данных, чтобы вручную увеличить поле версии. Таким образом, все остальные транзакции с оптимизацией будут аннулированы (откат). Проверка на версию также выполняется в конце транзакции для фиксации или откат транзакции.
Кажется ясным, но когда следует использовать режимы OPTIMISTIC
или OPTIMISTIC_FORCE_INCREMENT
? Единственный критерий, который я вижу, заключается в применении режима OPTIMISTIC_FORCE_INCREMENT
, когда я хочу, чтобы транзакция имела приоритет над другими, потому что выбор этого режима откатит все остальные, выполняющие транзакции (если я хорошо понимаю меканизм).
Есть ли другая (-а) причина (ы), чтобы выбрать этот режим, а не OPTIMISTIC
режим?
Спасибо
Ответы
Ответ 1
Обычно вы никогда не будете использовать API lock() для оптимизации блокировки. JPA будет автоматически проверять любые столбцы версий при любом обновлении или удалении.
Единственная цель API lock() для оптимизации блокировки - это когда ваше обновление зависит от другого объекта, который не изменен/обновлен. Это позволяет вашей транзакции по-прежнему сбой, если другой объект изменяется.
Когда это сделать, это зависит от приложения и варианта использования. OPTIMISTIC гарантирует, что другой объект не был обновлен во время вашего фиксации. OPTIMISTIC_FORCE_INCREMENT гарантирует, что другой объект не будет обновлен, и увеличит его версию при фиксации.
Оптимистическая блокировка всегда проверяется при фиксации, и нет гарантии успеха до фиксации. Вы можете использовать flush(), чтобы заблокировать блокировку базы данных раньше или вызвать более раннюю ошибку.
Ответ 2
Не бойся этим длинным ответом. Этот вопрос не прост.
По умолчанию JPA налагают
Прочитайте уровень изоляции, если вы не указали какую-либо блокировку (такое же поведение, как и при использовании LockModeType.NONE
).
Для чтения прочитанного требуется не существование явления Dirty read. Просто T1 может видеть только изменения, сделанные T2 после того, как T2 совершит.
Использование оптимистической блокировки в JPA повышает уровень изоляции до
Повторно читается.
Если T1 считывает некоторые данные в начале и в конце транзакции, повторное считывание гарантирует, что T1 видит одни и те же данные, даже если T2 изменил данные и зафиксировал их в середине T1.
И здесь идет сложная часть. JPA обеспечивает повторное считывание простейшим способом: предотвращение Неповторимое явление чтения. JPA недостаточно сложна, чтобы хранить снимки ваших чтений. Это просто предотвращает повторное чтение, увеличивая исключение (если данные изменились с первого чтения).
Вы можете выбрать один из двух вариантов оптимизации:
Какая разница между двумя?
Позвольте мне проиллюстрировать примеры этого объекта 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
}
Теперь предположим, что у нас есть один Человек с именем John, хранящийся в базе данных. Мы читаем этого человека в T1, но изменим его имя на Майка во второй транзакции 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 [[email protected]] 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 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 new car. john1 in stale state. That 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, но не наоборот.
Ответ 3
Как объяснено в этот пост, LockModeType.OPTIMICTIC
страдает от проблем с проверкой, так что вы лучше отключить его с помощью PESSIMISTIC_READ или PESSIMISTIC_WRITE.
С другой стороны, LockModeType.OPTIMICTIC_FORCE_INCREMENT
не страдает какой-либо проблемой несоответствия данных, и вы обычно используете ее для управления версии родительского объекта при изменении дочернего объекта.
Обратите внимание на эту статью для получения более подробной информации о том, как вы можете использовать LockModeType.OPTIMICTIC_FORCE_INCREMENT
или LockModeType.PESSIMISTIC_FORCE_INCREMENT
, чтобы версия родительского сущности учитывала также изменения дочернего объекта.