一尘不染

concurrency in hibernate

hibernate

我有一个servlet,它为用户做一些工作,然后减少用户的信用。当我实时查看数据库中的用户信用时,如果有来自同一用户的许多并发请求,则由于并发控制,信用被错误地扣除。假设我有一台服务器,并且数据库管理处于hibernate状态。我正在使用事务控制来处理整个请求,请参见代码以获取详细信息。我有几个问题:

  1. 当面对来自同一用户的多个并发请求时,为什么db中的信用计数器到处跳?为什么我的交易控制不起作用?

  2. 如果在检索用户帐户然后尝试更新之后修改了基础数据,为什么我没有得到任何数据HibernateException(eg.StaleObjectException)

  3. 我的交易跨整个用户请求,是否有更好的方法?请批评。如果您觉得我做错了所有事情,请随时重写示例代码结构。

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


阅读 288

收藏
2020-06-20

共1个答案

一尘不染

您有两种方法来处理这种情况: 悲观主义者 锁定或 乐观主义者 锁定。但是您似乎都不使用两者,这可能解释了错误的行为。

  • 使用乐观锁定,Hibernate将检查在读取和保存用户帐户之间是否没有更改用户帐户。然后,并发事务可能会失败并回滚。

  • 使用悲观锁定,您可以在读取行时锁定该行,并且仅在事务完成时才将其解锁。这样可以防止并发事务读取会过时的数据。

刷新实体可能会读取新数据,也可能不会读取新数据,这取决于当前事务是否已经提交,但这也不是解决方案。由于似乎还创建了用户帐户(如果该帐户不存在),因此您不能如此轻松地应用悲观主义者锁定。我建议您然后使用乐观锁定(例如,使用时间戳来检测并发修改)。

阅读关于SO的另一个有关悲观主义者和乐观主义者锁定的问题。还可以查看hibernate章节“
事务和并发
”和“
hibernate注释 ”。

它应该是一样简单加入@Version上的相应字段中,optimisticLockStrategy
默认值VERSION(一个单独的列被使用)。

-更新-

您可以测试它是否可以在测试用例中使用。我创建了一个简单的实体CounterIDvalue以及version多个领域。

 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
2020-06-20