[英]concurrency in hibernate
我有一个servlet为用户做一些工作,然后减少用户的信用。 当我实时查看数据库中用户的信用时,如果来自同一用户的并发请求很多,则由于并发控制而导致信用额被错误地扣除。 T假设我有一个服务器,并且使用的数据库管理是休眠。 我正在使用事务控制来跨越整个请求,请参阅代码以获取详细信息。 我有几个问题:
为什么当面对来自同一用户的许多并发请求时,db中的信用计数器会跳到各处? 为什么我的交易控制不起作用?
如果我在检索用户帐户后修改了基础数据,然后尝试更新它,为什么我没有得到任何HibernateException(eg.StaleObjectException)
?
我有整个用户请求的事务跨度,有更好的方法吗? 请批评。 如果您觉得我做错了,请随意重写示例代码结构。
Main servlet class: protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try{ Manager.beginTransaction(); cmdDowork(request, response); Manager.commitTransaction(); }catch(Exception exp){ Manager.rollbackTransaction(); exp.printStackTrace(); } finally{ Manager.closeSession(); } } public void cmdDowork(){ try{ UserAccount userAccount = lazyGetUserAccount(request.getParameter("userName")); doWorkForUser(userAccount);//time and resource consuming process if(userAccount!=null) { decUserAccountQuota(userAccount); } }catch (HibernateException e){ e.printStackTrace(); } } public static UserAccount lazyGetUserAccount(String userName) { UserAccount userAccount = Manager.getUserAccount(userName); if(userAccount == null){ userAccount = new UserAccount(userName); userAccount.setReserve(DEFAULT_USER_QUOTA); userAccount.setBalance(DEFAULT_USER_QUOTA); Manager.saveUserAccount(userAccount); } return userAccount; } private boolean decUserAccountQuota(UserAccount userAccount) { if(userAccount.getBalance()Edit: code I used to test optimistic locking as suggested by the answer, I am not getting a any StaleObjectException, the update were committed successfully.. Session em1=Manager.sessionFactory.openSession(); Session em2=Manager.sessionFactory.openSession();
em1.getTransaction().begin(); em2.getTransaction().begin(); UserAccount c1 = (UserAccount)em1.get( UserAccount.class, "jonathan" ); UserAccount c2 = (UserAccount)em2.get( UserAccount.class, "jonathan" ); c1.setBalance( c1.getBalance() -1 ); em1.flush(); em1.getTransaction().commit(); System.out.println("balance1 is "+c2.getBalance()); c2.setBalance( c2.getBalance() -1 ); em2.flush(); // fail em2.getTransaction().commit(); System.out.println("balance2 is "+c2.getBalance());
您有两种方法可以处理这种情况:使用悲观锁定或使用乐观主义锁定。 但是你似乎没有使用两者,这可能解释了不正确的行为。
通过乐观锁定,Hibernate将检查用户帐户在读取和保存之间是否未被更改。 然后,并发事务可能会失败并被回滚。
使用悲观锁定时,您在读取行时会锁定该行,并且只有在事务完成时才会解锁该行。 这可以防止并发事务读取过时的数据。
刷新实体可以根据当前事务是否已经提交来读取或不读取新数据,但也不是解决方案。 因为您似乎也创建了用户帐户(如果它不存在),所以您无法轻松应用悲观锁定。 我建议你使用乐观锁定(并使用例如时间戳来检测并发修改)。
阅读关于悲观主义和乐观主义锁定的其他问题 。 另请参阅hibernate章节“ 事务和并发 ”和“ 休眠注释 ”。
它应该像在相应字段上添加@Version
一样简单, optimisticLockStrategy
默认值是VERSION
(使用单独的列)。
- 更新 -
您可以测试它是否在测试用例中有效。 我创建了一个带有ID
, value
和version
字段的简单实体Counter
。
public class Counter implements Serializable {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Basic(optional = false)
@Column(name = "ID")
private Integer id;
@Column(name = "VALUE")
private Integer value;
@Column(name = "VERSION")
@Version
private Integer version;
...
}
如果按顺序更新一个实体,则可以:
id = insertEntity( ... );
em1.getTransaction().begin();
Counter c1 = em1.find( Counter.class, id );
c1.setValue( c1.getValue() + 1 );
em1.flush();
em1.getTransaction().commit();
em2.getTransaction().begin();
Counter c2 = em2.find( Counter.class, id );
c2.setValue( c2.getValue() + 1 );
em2.flush(); // OK
em2.getTransaction().commit();
我得到一个value=2
且version=2
实体。
如果我模拟两个并发更新:
id = insertEntity( ... );
em1.getTransaction().begin();
em2.getTransaction().begin();
Counter c1 = em1.find( Counter.class, id );
Counter c2 = em2.find( Counter.class, id );
c1.setValue( c1.getValue() + 1 );
em1.flush();
em1.getTransaction().commit();
c2.setValue( c2.getValue() + 1 );
em2.flush(); // fail
em2.getTransaction().commit();
然后第二次冲洗失败:
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=?
Hibernate: update COUNTER set VALUE=?, VERSION=? where ID=? and VERSION=?
Dec 23, 2009 11:08:46 AM org.hibernate.event.def.AbstractFlushingEventListener performExecutions
SEVERE: Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [org.ewe.Counter#15]
at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1765)
这是因为SQL语句中的实际参数是:
update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0
--> 1 row updated
update COUNTER set VALUE=1, VERSION=1 where ID=xxx and VERSION=0
--> 0 row updated, because version has been changed in between
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.