В Hibernate, пессимистическая блокировка, реализована блокировками уровня баз данных. То
есть при использовании pessimistic
locking имеет смысл глянуть, прежде всего, на
то, какая СУБД используется, на основе каких блокировок самой СУБД, могут реализовываться механизмы блокировки Hibernate. Если тип блокировки не
поддерживается СУБД, то hibernate
пытается
выбрать альтернативный вид блокировки, а
не вызывать exception, что в свою очередь делает код программы более
гибким, и позволяет нам использовать, различные Базы Данных. И так pessimistic locking
случается, когда вы считываете данные
(Сущность - Entity) и пытаетесь
овладеть данными для монопольного владения или изменения Сущности,
блокировка удерживается пока, транзакция
в hibrnate сессии не окончится, так в теории,
перейдем к практике. В Hibernate существует специальный класс LockOptions, который позволяет нам выбрать тип
блокировки. Обычно экземпляр этого класса используется в следующих методах:
Session.load(Class, id, LockOptions);
Session.get(Class, id, LockOptions);
Session.lock(Object, LockOptions);
Session.refresh( Object, LockOptions);
Query.setLockMode(alias, LockOptions);
Выбрать
режим блокировки позволяет нам метод
.setLockMode(), который принимает один единственный параметр типа LockMode.
И
так, какие уровни блокировки у нас есть,
давайте начнем с рассмотрения
оптимистичных блокировок и закончим пессимистическими блокировками:
LockMode.NONE - не используются, ни какие
блокировки баз данных на уровне запроса, по умолчанию load() и get() используют
LockMode.NONE, если запрашивается объект(Entity) и он существует в любом кэше
hibernate, то используется кэш. Если нет
объекта в кэше, то переходит в режим
блокировки LockMode.READ и формируется sql запрос.
Является уровнем блокировки по умолчанию в hibernate.
LockMode.READ - блокировка используется обычно
для чтения сущности из Базы Данных, заносит в кэш сущность. Не
используются, ни какие внутренние
блокировки баз данных на уровне sql
запроса.
LockMode.OPTIMISTIC – блокировка используется
обычно для принудительной проверки версионности у сущности, случается эта
проверка, когда заканчивается транзакция, смотрим на прошлый пример с двумя
транзакциями:
….
session =
sessionFactory.openSession();
//начало 2-й транзакции.
session.getTransaction().begin();
LockOptions
options = new LockOptions();
options.setLockMode(LockMode.OPTIMISTIC);
person =
(Person)session.get(Person.class,id,options);
System.out.format("Transaction2 before change Person: %s\n",
person);
try {
//ждем пока 1-я транзакция изменит
данные и завершится.
thread.join();
} catch
(InterruptedException e) {
e.printStackTrace(); session.getTransaction().rollback();
}
System.out.format("Transaction2 LockMode.OPTIMISTIC Person:
%s\n", person);
/* здесь произойдет вызов проверки
версионности и будет вызвана исключительная ситуация org.hibernate.OptimisticLockException. */
session.getTransaction().commit();
смотрим на лог вывода программы:
Transaction2 before change Person: Person{id=1, name='Vitaly',
version1=0}
Hibernate: update Person1 set MyName=?, MyText=?, version1=? where id=?
and version1=?
Transaction1 after change Person: Person{id=1, name='Person
Transaction1', version1=1}
Transaction2 LockMode.OPTIMISTIC Person: Person{id=1, name='Vitaly',
version1=0}
Hibernate: select version1 from Person1 where id =? – Проверка версионности
Exception in thread "main"
org.hibernate.OptimisticLockException: Newer version [1] of entity
[[org.vit.ch1.Person#1]] found in database
LockMode.OPTIMISTIC_FORCE_INCREMENT - блокировка
используется обычно для принудительной проверки версионности у сущности и
автоматического увеличения поля версионности,
случается эта проверка, когда заканчивается транзакция, смотрим на код:
session = sessionFactory.openSession();
//начало 2-й транзакции.
session.getTransaction().begin();
LockOptions
options = new LockOptions();
options.setLockMode(LockMode.OPTIMISTIC_FORCE_INCREMENT);
person =
(Person)session.get(Person.class,id,options);
System.out.format("Transaction2
before change Person: %s\n", person);
try {
//ждем пока 1-я транзакция
изменит данные и завершится.
thread.join();
} catch
(InterruptedException e) {
e.printStackTrace();
session.getTransaction().rollback();
}
System.out.format("Transaction2 LockMode.
OPTIMISTIC_FORCE_INCREMENT Person:
%s\n", person);
/* здесь произойдет вызов проверки
версионности + автоинкремент версионного поля и будет вызвана исключительная ситуация org.hibernate.StaleObjectStateException. */
session.getTransaction().commit();
смотрим на лог вывода программы:
Transaction2 before change Person: Person{id=1, name='Vitaly',
version1=0}
Hibernate: update Person1 set MyName=?, MyText=?, version1=? where id=?
and version1=?
Transaction1 after change Person: Person{id=1, name='Person
Transaction1', version1=1}
Transaction2 LockMode. OPTIMISTIC_FORCE_INCREMENT Person: Person{id=1,
name='Vitaly', version1=0}
Hibernate: update Person1 set version1=? where id=? and version1=? - Попытка увеличить версионное поле
Exception in thread "main" org.hibernate.StaleObjectStateException:
Row was updated or deleted by another transaction (or unsaved-value mapping was
incorrect): [org.vit.ch1.Person#1]
LockMode.WRITE - случается, когда объект
сущности(Entity) обновляется (update) или вставляется (insert), этот уровень
блокировки используется только внутренним механизмом hibernate и его
использовать в методах запрещено.
LockMode.PESSIMISTIC_READ - делает
блокировку строки на чтение, на время блокировки
строки первой транзакцией, ни какая вторая транзакция не может её обновить. Я использую postgresql и у неё есть
специальная sql команда
для блокирования строки на чтение, SELECT
… FOR SHARE смотрим код:
session =
sessionFactory.openSession();
//начало 2-й транзакции.
session.getTransaction().begin();
LockOptions
options = new LockOptions();
options.setLockMode(LockMode.PESSIMISTIC_READ);
person =
(Person)session.get(Person.class,id,options);
System.out.format("Transaction2 before
change Person: %s\n", person);
try {
/*ждем пока 1-я транзакция
изменит данные, но строка заблокирована 2-й транзакцией, здесь происходит взаимоблокировка
(deadlock).*/
thread.join();
} catch
(InterruptedException e) {
e.printStackTrace();
session.getTransaction().rollback();
}
// суда мы ни когда не попадем из-за
взаимоблокировки!!!
System.out.format("Transaction2 LockMode.PESSIMISTIC_READ Person: %s\n",
person);
Лог программы:
Hibernate: select person0_.id as id0_0_, person0_.MyName as MyName0_0_,
person0_.MyText as MyText0_0_, person0_.version1 as version4_0_0_ from Person1
person0_ where person0_.id=? for share
Transaction2 before change Person: Person{id=1, name='Vitaly'}
Hibernate: update Person1 set MyName=?, MyText=?, version1=? where id=?
and version1=?
// все здесь
произошёл deadlock
LockMode.PESSIMISTIC_WRITE - блокировка использует
механизм внутренний монопольной блокировки Баз Данных на уровне запроса -
select ... for update , т. е. запрошенная запись (Entity) блокируется для
дальнейшего изменения, если запись не может быть заблокирована немедленно, то
происходит ожидание освобождения данных. Смотрим на листинг кода:
session = sessionFactory.openSession();
//начало 2-й транзакции.
session.getTransaction().begin();
LockOptions
options = new LockOptions();
options.setLockMode(LockMode.PESSIMISTIC_WRITE);
person =
(Person)session.get(Person.class,id,options);
System.out.format("Transaction2 before change Person: %s\n",
person);
try {
/*ждем пока 1-я транзакция
изменит данные, но строка заблокирована 2-й транзакцией, здесь происходит
взаимоблокировка (deadlock).*/
thread.join();
} catch
(InterruptedException e) {
e.printStackTrace();
session.getTransaction().rollback();
}
// суда мы ни когда не попадем!!!
System.out.format("Transaction2 LockMode.PESSIMISTIC_WRITE Person:
%s\n", person);
Лог программы:
Hibernate:
select person0_.id as id0_0_, person0_.MyName as MyName0_0_, person0_.MyText as
MyText0_0_, person0_.version1 as version4_0_0_ from Person1 person0_ where
person0_.id=? for update
Transaction2
before change Person: Person{id=1, name='Vitaly', version1=0}
Hibernate:
update Person1 set MyName=?, MyText=?, version1=? where id=? and version1=?
// все здесь произошёл deadlock
LockMode.PESSIMISTIC_FORCE_INCREMENT
- блокировка подобна блокировке LockMode.PESSIMISTIC_WRITE, т.е. так же выполняет запрос select ... for update, плюс принудительно увеличивает версионное поле в объекте сущности(Entity), да же если в текущей транзакции, не менялось состояние полей у сущности. Смотрим листинг примера:
session =
sessionFactory.openSession();
//начало 2-й транзакции.
session.getTransaction().begin();
LockOptions
options = new LockOptions();
options.setLockMode(LockMode.PESSIMISTIC_FORCE_INCREMENT);
person =
(Person)session.get(Person.class,id,options);
System.out.format("Transaction2 before change Person: %s\n",
person);
try {
/*ждем пока 1-я транзакция
изменит данные, но строка заблокирована 2-й транзакцией, здесь происходит
взаимоблокировка (deadlock).*/
thread.join();
} catch
(InterruptedException e) {
e.printStackTrace();
session.getTransaction().rollback();
}
// суда мы ни когда не попадем!!!
System.out.format("Transaction2 LockMode.PESSIMISTIC_WRITE Person:
%s\n", person);
Лог программы:
Hibernate:
select person0_.id as id0_0_, person0_.MyName as MyName0_0_, person0_.MyText as
MyText0_0_, person0_.version1 as version4_0_0_ from Person1 person0_ where
person0_.id=? for update
Hibernate:
update Person1 set version1=? where id=? and version1=?
– автоинкрементен версионности
Transaction2
before change Person: Person{id=1, name='Vitaly', version1=1}
2014-09-29
20:09:08,932 DEBUG [org.hibernate.SQL] - <update Person1 set MyName=?,
MyText=?, version1=? where id=? and version1=?>
Hibernate:
update Person1 set MyName=?, MyText=?, version1=? where id=? and version1=?
LockMode.FORCE – эту блокировку не советуют использовать, используйте блокировку LockMode.PESSIMISTIC_FORCE_INCREMENT , блокировка объявлена как deprecated.
LockMode.UPGRADE – блокировка
объявлена как deprecated, подобна блокировке LockMode.PESSIMISTIC_WRITE, используйте её.
LockMode.UPGRADE_NOWAIT - блокировка похожа на LockMode.PESSIMISTIC_WRITE, но только использует запрос select … for update nowait, отличие в том, что если запись не может быть заблокирована сразу, то происходит исключение. На моей памяти запрос select … for update nowait поддерживает только БД Oracle, соответственно если БД не поддерживает такие запросы, то
LockMode.UPGRADE_NOWAIT преобразуется к блокировке
LockMode.PESSIMISTIC_WRITE.
Очень полезно. Спасибо.
ОтветитьУдалитьОчень полезно. Спасибо.
ОтветитьУдалить+++ полезно
ОтветитьУдалить