简体   繁体   English

hibernate中的并发性

[英]concurrency in hibernate

I have a servlet that does some work for user and then decrement user's credit. 我有一个servlet为用户做一些工作,然后减少用户的信用。 When I watch user's credit in the database in real time, if there are many concurrent requests from the same user, the credit has been deducted incorrectly due to concurrency control. 当我实时查看数据库中用户的信用时,如果来自同一用户的并发请求很多,则由于并发控制而导致信用额被错误地扣除。 T Assume I have one server and database management used is hibernate. T假设我有一个服务器,并且使用的数据库管理是休眠。 I am using transaction control to span the whole request, please see code for detail. 我正在使用事务控制来跨越整个请求,请参阅代码以获取详细信息。 I have several questions: 我有几个问题:

  1. Why are the credit counter in db jumping all over the place when facing many concurrent request from same user? 为什么当面对来自同一用户的许多并发请求时,db中的信用计数器会跳到各处? why isn't my transaction control working? 为什么我的交易控制不起作用?

  2. If underlying data was modified after I retrieved user account and then attempt to update it, why didn't I get any HibernateException(eg.StaleObjectException) ? 如果我在检索用户帐户后修改了基础数据,然后尝试更新它,为什么我没有得到任何HibernateException(eg.StaleObjectException)

  3. I have transaction span across the full user request, is there a better way? 我有整个用户请求的事务跨度,有更好的方法吗? Please critique. 请批评。 Feel free to rewrite the sample code structure if you feel I'm doing the whole thing wrong. 如果您觉得我做错了,请随意重写示例代码结构。

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

You have two ways to handle this situation: either with pessimist locking or with optimist locking. 您有两种方法可以处理这种情况:使用悲观锁定或使用乐观主义锁定。 But you seem to use neither of both, which explain probably the incorrect behaviour. 但是你似乎没有使用两者,这可能解释了不正确的行为。

  • With optimistic locking, Hibernate will check that the user account wasn't altered between the time it was read and saved. 通过乐观锁定,Hibernate将检查用户帐户在读取和保存之间是否未被更改。 A concurrent transaction may then fail and be rolled back. 然后,并发事务可能会失败并被回滚。

  • With pessimistic locking, you lock the row when you read it and it's unlocked only when transaction completes. 使用悲观锁定时,您在读取行时会锁定该行,并且只有在事务完成时才会解锁该行。 This prevent a concurrent transaction to read data that would become stale. 这可以防止并发事务读取过时的数据。

Refreshing the entity may read new data or not depending whether the current transaction has already been committed or not, but is not a solution neither. 刷新实体可以根据当前事务是否已经提交来读取或不读取新数据,但也不是解决方案。 Because you seem to also create the user account if it doesn't exist, you can't apply pessimist locking so easily. 因为您似乎也创建了用户帐户(如果它不存在),所以您无法轻松应用悲观锁定。 I would suggest you use optimistic locking then (and use for instance a timestamp to detect concurrent modifications). 我建议你使用乐观锁定(并使用例如时间戳来检测并发修改)。

Read this other question on SO about pessimist and optimist locking. 阅读关于悲观主义和乐观主义锁定的其他问题 Have also a look at hibernate chapter " transaction and concurrency " and " hibernate annotations ". 另请参阅hibernate章节“ 事务和并发 ”和“ 休眠注释 ”。

It should be as simple as adding @Version on the corresponding field, the optimisticLockStrategy default value is VERSION (a separate column is used). 它应该像在相应字段上添加@Version一样简单, optimisticLockStrategy 默认值VERSION (使用单独的列)。

-- UPDATE -- - 更新 -

You can test whether it works in a test case. 您可以测试它是否在测试用例中有效。 I've created a simple entity Counter with an ID , value , and version fields. 我创建了一个带有IDvalueversion字段的简单实体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;
    ...
}

If you update one entity sequentially it works: 如果按顺序更新一个实体,则可以:

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

I get one entity with value=2 and version=2 . 我得到一个value=2version=2实体。

If I simulate two concurrent updates: 如果我模拟两个并发更新:

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

then the 2nd flush fails: 然后第二次冲洗失败:

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)

This is so because the actual parameters in the SQL statements are: 这是因为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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM