2009-12-24 7 views
9

Używam Hibernate, próbując symulować 2 równoczesne aktualizacje do tego samego wiersza w bazie danych.przekroczony limit czasu oczekiwania na hibernację blokady;

Edytuj: Wprowadziłem em1.getTransaction(). Commit, aby być zaraz po em1.flush(); Nie otrzymuję żadnego StaleObjectException, obie transakcje zostały pomyślnie zatwierdzone.

Session em1=Manager.sessionFactory.openSession(); 
Session em2=Manager.sessionFactory.openSession(); 

em1.getTransaction().begin(); 
em2.getTransaction().begin(); 

UserAccount c1 = (UserAccount)em1.get(UserAccount.class, "root"); 
UserAccount c2 = (UserAccount)em2.get(UserAccount.class, "root"); 

c1.setBalance(c1.getBalance() -1); 
em1.flush(); 
System.out.println("balance1 is "+c2.getBalance()); 
c2.setBalance(c2.getBalance() -1); 
em2.flush(); // fail 

em1.getTransaction().commit(); 
em2.getTransaction().commit(); 

System.out.println("balance2 is "+c2.getBalance()); 

Otrzymuję następujący wyjątek na em2.flush(). Czemu?

2009-12-23 21:48:37,648 WARN JDBCExceptionReporter:100 - SQL Error: 1205, SQLState: 41000 
2009-12-23 21:48:37,649 ERROR JDBCExceptionReporter:101 - Lock wait timeout exceeded; try restarting transaction 
2009-12-23 21:48:37,650 ERROR AbstractFlushingEventListener:324 - Could not synchronize database state with session 
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update 
    at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126) 
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114) 
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66) 
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275) 
    at org.hibernate.persister.entity.AbstractEntityPersister.processGeneratedProperties(AbstractEntityPersister.java:3702) 
    at org.hibernate.persister.entity.AbstractEntityPersister.processUpdateGeneratedProperties(AbstractEntityPersister.java:3691) 
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:147) 
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279) 
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263) 
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168) 
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321) 
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50) 
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1028) 
    at com.ch.whoisserver.test.StressTest.main(StressTest.java:54) 
Caused by: java.sql.BatchUpdateException: Lock wait timeout exceeded; try restarting transaction 
    at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1213) 
    at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:912) 
    at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70) 
    at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268) 
    ... 10 more 

Odpowiedz

20

Cóż, starasz się dostać do impasu i masz sukces :-)

  1. Transaction1 zaczyna, aktualizacje (i zamki) wiersz z jednostką.
  2. Transakcja2 próbuje zrobić to samo, ale nie może, ponieważ wiersz jest nadal zablokowany. Więc czeka (i czeka i czeka), dopóki limit czasu zostanie przekroczony

Prawdziwe życie symulacja musiałby 1. i 2. kierownika jednostki oraz stosownych aktualizacji/transakcje w oddzielnych wątkach. W ten sposób:

  1. Transakcja1 uruchamia, aktualizuje (i blokuje) wiersz z twoją jednostką.
  2. Transakcja2 próbuje zrobić to samo, ale nie może, ponieważ wiersz jest nadal zablokowany. Więc czeka (i czeka i czeka) ...
  3. Tymczasem Transaction1 jest zaangażowana i zwolnieniu blokady
  4. TRANSAKCJI2 może teraz przystąpić

Należy zauważyć, że w tym punkcie (# 4 powyżej), że ty nadpisywać zmiany wprowadzone przez Transaction1. Hibernate może używać optimistic locking, a także pessimistic locking, aby temu zapobiec.

Aktualizacja (w oparciu o komentarzu):

Jeśli jednostka jest wersji, TRANSAKCJI2 (# 4 powyżej) nie powiedzie się. Jednak Twój kod wysłany nie dotrze do tego punktu, ponieważ Transaction2 nie może uzyskać blokady, jak wyjaśniono powyżej. Jeśli chcesz przetestować, że kontrola specjalnie optymistyczna wersja działa można wykonać następujące czynności:

  1. Uzyskanie EM1 rozpocząć transakcję uzyskać podmiot, popełnić transakcję, bliską EM1.
  2. Uzyskaj em2, rozpocznij transakcję, uzyskaj swój podmiot, zaktualizuj swój podmiot, dokonaj transakcji, zamknij em2.
  3. Uzyskaj em3, rozpocznij transakcję, spróbuj zaktualizować encję załadowaną w kroku 1 - test powinien zakończyć się niepowodzeniem.
+0

Właściwie próbuję napisać przypadek testowy, aby zobaczyć, czy działa optymistyczne blokowanie, przedmiot UserAccount, o którym mowa, używa wersji, zobacz szczegółowe pytanie http://stackoverflow.com/questions/1938671/concurrency- w trybie hibernacji, w tym przypadku z dwoma wątkami transakcja2 uzyskałby wyjątek staledObjectException w celu wykrycia zmiany w podstawowych danych? – user217631

+0

@unknown Tak, to by było. – KLE

+0

Zaktualizowałem swoją odpowiedź powyżej - używanie dwóch wątków nie jest dobrym sposobem na przetestowanie ** specjalnie ** optymistycznego blokowania (z powodu nieprzewidywalności) – ChssPly76