Гость
Map
Форумы / Java [игнор отключен] [закрыт для гостей] / Hibernate блокировки - как работает пример? / 12 сообщений из 12, страница 1 из 1
07.05.2021, 16:06
    #40068974
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
Реализация примера из официальной доки по хиберу - оптимистическая блокировка с исключением определенного поля при проверки ентити на изменение (https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#locking-optimistic (глава - Excluding attributes)).
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
public class TestExcludingTrackingForPropertyWithOptimistikLocking2 extends BaseTest{

	public static void main(String[] args) {

		doInJPA(entityManager -> {

			Query query = entityManager.createQuery("Delete from Phone24");
			query.executeUpdate();
			
			Phone24 phone = new Phone24();
			phone.setId(1L);
			phone.setNumber("+123-456-3333");
			phone.setCallCount(0);
			
			entityManager.persist(phone);
			
		});
		
		doInJPA(entityManager -> {
			
			int isolationLevel = entityManager.unwrap(Session.class).doReturningWork(Connection::getTransactionIsolation);
			System.out.println("Isolation level is [" + isolationLevel + "]");
			
			Phone24 phone = entityManager.find(Phone24.class, 1L);
			phone.setNumber("+123-456-0000");

			doInJPA(entityManager2 -> {
				Phone24 _phone = entityManager2.find(Phone24.class, 1L);
				_phone.incrementCallCount();
				System.out.println("Bob changes the Phone call count");
			});
			
			System.out.println("Alice changes the Phone number");
			
		});
		
		entityManagerFactory.close();
		
	}
}

@Entity(name = "Phone24")
@Table(name = "Phones24")
class Phone24 {

	@Id
	private Long id;
	@Column(name = "`number`")
	private String number;
	@OptimisticLock(excluded = true)
	private long callCount;
	@Version
	private Long version;

	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	public String getNumber() {
		return number;
	}
	public void setNumber(String number) {
		this.number = number;
	}
	public long getCallCount() {
		return callCount;
	}
	public void setCallCount(long callCount) {
		this.callCount = callCount;
	}
	public Long getVersion() {
		return version;
	}
	public void setVersion(Long version) {
		this.version = version;
	}

	public void incrementCallCount() {
		this.callCount++;
	}
}


Метод DoInJPA:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
protected static void doInJPA(Consumer<EntityManager> code) {

		if (entityManagerFactory == null){
			buildDefaultEntityManagerFactory();
		}
		
		EntityManager entityManager = null;
		EntityTransaction txn = null;
		try {
			entityManager = entityManagerFactory.createEntityManager();

			txn = entityManager.getTransaction();
			txn.begin();

			code.accept(entityManager);

			txn.commit();
		} catch (RuntimeException e) {
			if (txn != null && txn.isActive())
				txn.rollback();
			throw e;
		} finally {
			if (entityManager != null) {
				entityManager.close();
			}
		}
	}


Здесь все работает как положено.
1) В транзацкции №1 создается запись в бд.
2) В транзакции №2 эта запись читается, после чего изменяется состояние entity (поле "номер").
3) Затем запускается вложенная транзакция №3, которая меняет поле callCount и завершается.
4) В конце завершается транзакция №2.

Консоль:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
Hibernate: delete from Phones24
Hibernate: insert into Phones24 (callCount, `number`, version, id) values (?, ?, ?, ?)
Isolation level is [4]
Hibernate: select phone24x0_.id as id1_182_0_, phone24x0_.callCount as callcoun2_182_0_, phone24x0_.`number` as number3_182_0_, phone24x0_.version as version4_182_0_ from Phones24 phone24x0_ where phone24x0_.id=?
Hibernate: select phone24x0_.id as id1_182_0_, phone24x0_.callCount as callcoun2_182_0_, phone24x0_.`number` as number3_182_0_, phone24x0_.version as version4_182_0_ from Phones24 phone24x0_ where phone24x0_.id=?
Bob changes the Phone call count
Hibernate: update Phones24 set callCount=?, `number`=?, version=? where id=? and version=?
Alice changes the Phone number
Hibernate: update Phones24 set callCount=?, `number`=?, version=? where id=? and version=?


Мне интеречно, что происходит, когда мы помещаем код транзакции №1 в начало транзакции №2:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
public class TestExcludingTrackingForPropertyWithOptimistikLocking2 extends BaseTest{

	public static void main(String[] args) {

		doInJPA(entityManager -> {

			int isolationLevel = entityManager.unwrap(Session.class).doReturningWork(Connection::getTransactionIsolation);
			System.out.println("Isolation level is [" + isolationLevel + "]");
			
			Query query = entityManager.createQuery("Delete from Phone24");
			query.executeUpdate();
			
			Phone24 phone = new Phone24();
			phone.setId(1L);
			phone.setNumber("+123-456-3333");
			phone.setCallCount(0);
			
			entityManager.persist(phone);
                        entityManager.flush();
                        entityManager.clear();

			Phone24 phone = entityManager.find(Phone24.class, 1L);
			phone.setNumber("+123-456-0000");

			doInJPA(entityManager2 -> {
				Phone24 _phone = entityManager2.find(Phone24.class, 1L);
				_phone.incrementCallCount();
				System.out.println("Bob changes the Phone call count");
			});
			
			System.out.println("Alice changes the Phone number");
			
		});
		
		entityManagerFactory.close();
		
	}
}


Консоль:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
Isolation level is [4]
Hibernate: delete from Phones24
Hibernate: insert into Phones24 (callCount, `number`, version, id) values (?, ?, ?, ?)
Hibernate: select phone24x0_.id as id1_182_0_, phone24x0_.callCount as callcoun2_182_0_, phone24x0_.`number` as number3_182_0_, phone24x0_.version as version4_182_0_ from Phones24 phone24x0_ where phone24x0_.id=?
Hibernate: select phone24x0_.id as id1_182_0_, phone24x0_.callCount as callcoun2_182_0_, phone24x0_.`number` as number3_182_0_, phone24x0_.version as version4_182_0_ from Phones24 phone24x0_ where phone24x0_.id=?
Bob changes the Phone call count
Hibernate: update Phones24 set callCount=?, `number`=?, version=? where id=? and version=?

**** Тут мы долго висим *****

ERROR: Lock wait timeout exceeded; try restarting transaction
Exception in thread "main" javax.persistence.RollbackException: Error while committing the transaction
	at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104)
	at by.pva.hibernate.part01._myUtils.BaseTest.doInJPA(BaseTest.java:44)
	at by.pva.hibernate.part01.locking.optimistic.TestExcludingTrackingForPropertyWithOptimistikLocking2.lambda$1(TestExcludingTrackingForPropertyWithOptimistikLocking2.java:53)
	at by.pva.hibernate.part01._myUtils.BaseTest.doInJPA(BaseTest.java:42)
	at by.pva.hibernate.part01.locking.optimistic.TestExcludingTrackingForPropertyWithOptimistikLocking2.main(TestExcludingTrackingForPropertyWithOptimistikLocking2.java:33)
Caused by: javax.persistence.PessimisticLockException: could not execute statement
	at org.hibernate.internal.ExceptionConverterImpl.wrapLockException(ExceptionConverterImpl.java:273)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:108)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
	at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:65)
	... 5 more
Caused by: org.hibernate.PessimisticLockException: could not execute statement
	at org.hibernate.dialect.MySQLDialect$3.convert(MySQLDialect.java:537)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3449)
	at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3311)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3723)
	at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:201)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
	at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
	at java.util.LinkedHashMap.forEach(Unknown Source)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:102)
	at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1360)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:451)
	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3210)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2378)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
	... 4 more
Caused by: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:123)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1347)
	at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
	... 24 more

...
Рейтинг: 0 / 0
07.05.2021, 16:29
    #40068980
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
Пробовал расставить задержки и посмотреть, что происходит в бд:
Код: java
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
		doInJPA(entityManager -> {
			
			int isolationLevel = entityManager.unwrap(Session.class).doReturningWork(Connection::getTransactionIsolation);
			System.out.println("Isolation level is [" + isolationLevel + "]");

			Query query = entityManager.createQuery("Delete from Phone24");
			query.executeUpdate();
		
			System.out.println("after delete");
			try {
				Thread.sleep(30000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			Phone24 phonex = new Phone24();
			phonex.setId(1L);
			phonex.setNumber("+123-456-3333");
			phonex.setCallCount(0);
			
			entityManager.persist(phonex);
			entityManager.flush();
			entityManager.clear();
			
			System.out.println("after persist");
			try {
				Thread.sleep(30000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			Phone24 phone = entityManager.find(Phone24.class, 1L);
			phone.setNumber("+123-456-0000");

			doInJPA(entityManager2 -> {
				Phone24 _phone = entityManager2.find(Phone24.class, 1L);
				_phone.incrementCallCount();
				//_phone.setNumber("+7098765443"); // Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [by.pva.hibernate.part01.locking.optimistic.Phone24#1]
				System.out.println("Bob changes the Phone call count");
			});



В базе никакие изменения не происходили (удаление и сохранение новой записи). Но при этом Hiber выводит лог (delete ... ; insert ...;).
Правильно ли я понимаю, что до конца транзакции никакие фзические модификации бд не происходят,
а все это деле делается виртуально?
А эксепшен в последнем примере вызван тем, что встроенная транзакция №3 не может завершится из-за того,
что запись вроде еще не существует (она должна появиться после выполнения транзакции №2, но она в свою очередь ждет завершения транзакции №3)?
...
Рейтинг: 0 / 0
07.05.2021, 16:51
    #40068987
Hibernate блокировки - как работает пример?
Две транзакции не будут видеть изменения друг друга пока одна из них не закоммитит изменения. Однако если они изменяют одну и ту же строку (UPDATE/DELETE), то 2ая будет заблокирована и будет ждать пока первая не закончится.

Однако в твоем примере я не вижу такой ситуации пока. Может в таблице уже эта строка на момент когда ты запускаешь пример? Например, осталась с прошлых запусков?
...
Рейтинг: 0 / 0
07.05.2021, 17:15
    #40069001
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
Stanislav Bashkyrtsev, да она есть на момент запуска.
Но почему все работает, когда очистка таблицы и создание новой записи вынесена в отдельную транзакцию, и не работает когда все это совмещено? И в первом и во втором примере происходит изменение одной и той же энтити (записи в бд).
...
Рейтинг: 0 / 0
07.05.2021, 17:29
    #40069005
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
Да и вроде ж как для энтити используется оптимистичная блокировка. (flush mode и isolation lvl дефолтные, бд - MySql).
Так если Боб в своей транзакции (в первом рабочем примере) вместе с изменением количества вызовов изменит и номер - то сработает optimistic locking и выпадет соответсвующий эксепшн.
...
Рейтинг: 0 / 0
07.05.2021, 17:44
    #40069008
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
Интуиция подсказывает, что решение искать нужно где то тут:

В первом случае когда транзакции №2 и №3 изменяют одну и ту же запись, она существует в бд (очистка таблицы и вставка записи уже закомичены в перврой транзакции). Т.е. они работают с физически существующей записью.

Во втором же случае запись перед стартом существует, потом вторая транзакция удаляет все и создает новую запись и потом ее изменяет (я так понимаю где то во временных таблицах), а вот с чем работает транзакция №3 - с изначальной записью, которая была при запуске примера или записью, которую создала транзакция №2 (возможно это разные записи).

Это все догадки, я еще толком не понимаю как это работает ...
...
Рейтинг: 0 / 0
07.05.2021, 17:54
    #40069009
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
И еще момент, раз до конца транзакции изменения физически не происходят в таблице, но при этом лог хибернейта выводит, что изменения произошли (delete ..., insert ..., ), то получается что он отсылает все запросы (*.executeQuery()) в бд а она уже на своей стороне решает что с ними делать ? (Разруливает блокировки). Т.е. решение надо искать в доках СУБД?
...
Рейтинг: 0 / 0
07.05.2021, 19:05
    #40069018
PetroNotC Sharp
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
faustgreen,
Сабж о двух параллельных транзакциях. Зачем ты все запутал третьей?
...
Рейтинг: 0 / 0
07.05.2021, 21:08
    #40069023
Андрей Панфилов
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
faustgreen,

вам надо копать в направлении работы RR (repeatable read) в MySQL с оптимистичными блокировками хибера:
- то что во втором случае вылетает PessimisticLockException - судя по всем до запуска теста в БД что-то уже есть, и эту строку внутренняя транзакция видит
- а вот то что в первом случае все проходит чисто - мне не особо очевидно (видно опыт основной с Oracle и PostgreSQL, где RC по умолчанию, дает о себе знать): раз там RR, то финальный апдейт от внешней транзакции не видит обновление версии, которое произвела внутренняя транзакция, хотя чего оно (БД) не ругается на то что обновляемая строка поменялась для меня загадка (ибо нет опыта с RR и MySQL)

т.е.:

faustgreen

Здесь все работает как положено .
1) В транзацкции №1 создается запись в бд.
2) В транзакции №2 эта запись читается, после чего изменяется состояние entity (поле "номер").
3) Затем запускается вложенная транзакция №3, которая меняет поле callCount и завершается.
4) В конце завершается транзакция №2.


нифига оно не работает как положено
...
Рейтинг: 0 / 0
07.05.2021, 21:12
    #40069024
Hibernate блокировки - как работает пример?
faustgreen
Stanislav Bashkyrtsev, да она есть на момент запуска.
Но почему все работает, когда очистка таблицы и создание новой записи вынесена в отдельную транзакцию, и не работает когда все это совмещено? И в первом и во втором примере происходит изменение одной и той же энтити (записи в бд).
Это правильное замечание :) Т.к. в твоем случае FlushMode=AUTO, то Hibernate будет флушить изменения только в этих случаях:
1. Перед коммитом транзакции
2. Перед select'ом
3. Если вручную вызвать flush()

В твоем же коде ничего такого не происходит. Т.е. ты поменял Entity, однако UPDATE не произойдет сразу. В твоем случае он будет происходить только по выходу из doInJPA(). Поэтому 1ое обновление на самом деле запись не лочит. И 2ая транзакция спокойно обновляет запись.

А вот entityManager.createQuery("Delete from Phone24") - вот он сразу выполняется, т.к. запрос написан явно. Вот этот DELETE лочит запись и не дает 2ой транзакции ее обновить.
faustgreenВ первом случае когда транзакции №2 и №3 изменяют одну и ту же запись, она существует в бд (очистка таблицы и вставка записи уже закомичены в перврой транзакции).Если же таблица пустая на момент старта, то этот DELETE ничего не залочит, INSERT даже если и произойдет (зависит от ID Generation Strategy), то новую запись 2ая транзакция еще не увидит. Ну и т.к. UPDATE во 2ой транзакции ничего не увидит, то и ничего и не заблочит.
...
Рейтинг: 0 / 0
07.05.2021, 21:26
    #40069025
Hibernate блокировки - как работает пример?
Андрей Панфилов
а вот то что в первом случае все проходит чисто - мне не особо очевидно (видно опыт основной с Oracle и PostgreSQL, где RC по умолчанию, дает о себе знать): раз там RR, то финальный апдейт от внешней транзакции не видит обновление версии, которое произвела внутренняя транзакция, хотя чего оно (БД) не ругается на то что обновляемая строка поменялась для меня загадка (ибо нет опыта с RR и MySQL)
У него стоит exclude:
Код: java
1.
2.
@OptimisticLock(excluded = true)
private long callCount;

Т.е. версия в принципе не обновляется, так что независимо от уровня изоляции OptimisticLockingException мы не получим если обновляем только это поле.
...
Рейтинг: 0 / 0
07.05.2021, 23:00
    #40069038
faustgreen
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Hibernate блокировки - как работает пример?
Stanislav Bashkyrtsev, Андрей Панфилов спасибо! Общее направление понял, нужно будет почитать по этой теме.
...
Рейтинг: 0 / 0
Форумы / Java [игнор отключен] [закрыт для гостей] / Hibernate блокировки - как работает пример? / 12 сообщений из 12, страница 1 из 1
Целевая тема:
Создать новую тему:
Автор:
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]