Hibernate锁等待超时;

10

我正在使用Hibernate,试图模拟对数据库中同一行的两次并发更新。

编辑:我把em1.getTransaction().commit移动到em1.flush()后面;现在我没有收到StaleObjectException,这两个事务都成功提交了。

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());

em2.flush()上我遇到了以下异常。为什么会出现这种情况?

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
1个回答

22

嗯,你正在尝试进入死锁并且成功了 :-)

  1. Transaction1开始,更新(并锁定)您的实体行。
  2. Transaction2试图执行相同的操作,但无法进行,因为该行仍在被锁定。 因此它会等待(等待,等待...),直到超时。

真实生活仿真将具有第1个和第2个实体管理器以及单独的线程中适当的更新/事务。这样,您将拥有:

  1. Transaction1开始,更新(并锁定)您的实体行。
  2. Transaction2试图执行相同的操作,但无法进行,因为该行仍在被锁定。 因此它会等待(等待,等待...)...
  3. 同时Transaction1已经提交并释放了锁
  4. Transaction2现在可以继续进行

请注意,在那一点(#4 上面),您将覆盖由Transaction 1所做的更改。Hibernate可以使用乐观锁定悲观锁定来防止发生这种情况。

更新(基于评论):

如果该实体是有版本控制的,在Transaction2(#4 以上)将失败。但是,根据您发布的代码,您的代码并没有达到那个点,因为Transaction2无法获得锁定,就像上面所解释的那样。如果您想特别测试乐观版本控制是否正在工作,可以执行以下操作:

  1. 获取em1,开始事务,获取您的实体,提交事务,关闭em1。
  2. 获取em2,开始事务,获取您的实体,更新您的实体,提交事务,关闭em2。
  3. 获取em3,开始事务,尝试更新在步骤1中加载的实体-此时测试应该失败。

我正在尝试编写一个测试用例来验证乐观锁是否起作用,涉及的UserAccount对象正在使用版本控制,请参考此问题的详细信息:https://dev59.com/_krSa4cB1Zd3GeqPae0i,在这种情况下,如果有两个线程,transaction2会得到一个staledObjectException以检测底层数据的更改吗? - user217631
我已经更新了我的回答 - 使用两个线程不是测试乐观锁定的好方法(因为结果不可预测)。 - ChssPly76
在你的第二步中,你是不是指获取em2而不是获取em1? - user217631
是的,抱歉。所有3个步骤都应该有3个不同的实体管理器。 - ChssPly76
即使我一次只运行单个更新,我仍然会收到此异常。在它之前或同时没有正在运行的事务。这可能是什么原因? - Suhail Gupta
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接