Подтвердить что ты не робот

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 режим?

Спасибо

4b9b3361

Ответ 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 недостаточно сложна, чтобы хранить снимки ваших чтений. Это просто предотвращает повторное чтение, увеличивая исключение (если данные изменились с первого чтения).

Вы можете выбрать один из двух вариантов оптимизации:

  • 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
}

Теперь предположим, что у нас есть один Человек с именем 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, чтобы версия родительского сущности учитывала также изменения дочернего объекта.