При работе с реальными системами крайне важным моментом становится взаимодействие с базой данных (далее – БД) в многопоточной среде. Другими словами, мы должны корректно обрабатывать транзакции, избегая нарушения консистентности данных во время чтения и записи данных разными потоками. Для решения проблем, которые могут возникнуть в многопоточной среде, в Java Persistence API (далее – JPA) реализован механизм под названием Optimisitc Lock (далее – OL), который позволяет нескольким потокам одновременно изменять данные не мешая друг другу.
Давайте разберёмся, что же такое OL.
Для понимания этого механизма мы должны познакомиться с аннотацией @Version, которая ставится над полем в POJO классе. Чаще всего, это выглядит так:
@Version private Integer version;
@Entity public class Developer { @Id private Long id; private String firstName; private String lastName; private String specialty; @Version private Long version; ... }
Стоит помнить, что существует ряд правил использования данной аннотации:
- В БД мы должны иметь соответствующую колонку в основной таблице сущности
- В качестве поля для контроля версии могут быть использованы следующие типы данных:
int, long, short, Integer, Long, Short, java.sql.Timestamp - Класс может иметь только одно поле с аннотацией @Version
Также, большинство реализаций JPA поддерживают механизм OL и без использования данного атрибута, но, “хорошим тоном” считается включать эту аннотацию, если мы планируем использовать OL. Если мы попробуем использовать механизм OL без аннотации @Version и наша реализация JPA не поддерживает его автоматически, то мы получим PersistenceException.
Стоит помнить, что мы должны иметь доступ для чтения данного поля, но, только JPA отвечает за изменение данного поля.
JPA поддерживает следующие режимы OL (находятся в классе LockModeType):
OPTIMISTIC
Получает блок на чтение для всех сущностей. Которые имеют аннотацию @Version
рекомендуется использовать именно его, а не синоним (READ). Как только мы делаем запрос в этом режиме, провайдер JPA исключает возможность “грязного” чтения и не повторяющегося чтения (non-repeatable).
Т.е. если другая транзакция:
- изменила или удалила данные, но не закоммитила их
- изменила или полностью удалила
Мы получим OptimisticLockException
READ
Синоним OPTIMISTIC (не рекомендуется к использованию)
OPTIMISTIC_FORCE_INCREMENTWRITE
Имеет такое же поведение, как и OPTIMISTIC, но, дополнительно увеличивает значение с аннотацией @Version.
WRITE
Синоним OPTIMISTIC_FORCE_INCREMENTWRITE (не рекомендуется к использованию)
Рассмотрим некоторые примеры использования OL:
entityManager.find(Developer.class, id, LockModeType.OPTIMISTIC);
Developer developer = entityManager.find(Developer.class, id); entityManager.lock(developer, LockModeType.OPTIMISTIC);
Query query = entityManager.createQuery("from Developer where lastName = :lastName"); query.setParameter("lastName", lastName); query.setLockMode(LockModeType.OPTIMISTIC_INCREMENT); query.getResultList()
@NamedQuery(name="optimisticLock", query="SELECT d FROM Developer d WHERE d.specialty LIKE :specialty", lockMode = WRITE)