humor200

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  1 Posts :: 1 Stories :: 1 Comments :: 0 Trackbacks
乐观锁是hibernate用来识别update到数据库的对象是否是脏对象的功能锁,在get->operation->update这种非原子操作中这种并发风险非常常见,get同一个对象,按照相反顺序设置新值然后更新就会出现脏对象的覆盖。
可以按照如下配置设置乐观锁version,这样刚才的脏对象操作就会弹出 StaleObjectStateException 异常。配置如下(注解也可以):
<class name="com.hibe.hbm.Customer" table="CUSTOMERS" optimistic-lock="version">
<id name="id" column="ID" type="int">
<generator class="increment"/>
</id>
<version name="version" column="ver" type="int"></version>
<property name="name" column="NAME" type="string" not-null="true"/>
<property name="password" column="PASSWORD" type="string" not-null="true"/>
</class>
在po和db里也要增加相应的字段。
不要以为配置这个就万事大吉了,用10个线程操作更新同一个对象,几乎有9-10个都会失败,弹出异常,那怎么办呢,就要处理,不处理还不如不加锁呢,全失败了。
如果update的新值是绝对值比如表单form也无所谓了,不加锁覆盖了也行,加锁重试也是一样效果,可是要是那种getValue,然后+1 或者+2,+3,再update的覆盖就不行了,和业务逻辑违背。也就说你要知道要update的这个对象是怎么来的,中间做了什么,才得到的新值,这几个小操作作为原子操作,在重试的时候要还原回当初的业务逻辑。于是我写了个下面的小类:
/**
 * 更新hibernate对象,用此类更新可以实现脏对象自动重试更新功能
 * @author hongweizhang@cyou-inc.com
 * @param <T>
 */
public abstract class Updater<T> {
  private Logger logger = LoggerFactory.getLogger(Updater.class);
    /**
     * 构建实际对象
     * 
     * @return
     * @throws Exception
     */
    public abstract T makeObjectForUpdate() throws Exception;
    /**
     * 实际更新方法,已经实现脏对象更新自动重试功能。
     * @param object
     * @param session
     * @param c
     * @return
     * @throws Exception
     */
    private boolean update(Dao dao, int c) throws Exception {
    Object obj = null;
    try {
obj = makeObjectForUpdate();
if(c>=100) {    //不怕一万就怕万一 死循环
logger.error("thread:" + Thread.currentThread().getId() +",error: update retry over 100 times,object:" + obj + ",object class:" + obj.getClass());
return false;
}
dao.update(obj);
if(c>0) {
logger.info("thread:" + Thread.currentThread().getId() +",success: update retry success,object:" + obj + ",object class:" + obj.getClass() + ",times:" + c);
}
c++;
return true;
} catch (Exception e) {
if(e instanceof Exception) {
c++;
logger.error("thread:" + Thread.currentThread().getId() +",StaleObjectStateException,object:"+obj+",class:"+obj.getClass());
update(dao, c);
}
return false;
} finally {
}
    }
    
    /**
     * 更新数据库
     * @param object
     * @param session
     * @throws Exception
     */
    public boolean update(Dao dao) throws Exception{
    return update(dao, 0);
}
}
使用方法调用端如下(多线程测试,实际使用去掉多线程):
public void update() throws Exception {
        try {
        for(int i=0; i < 10; i ++) {
        new Thread(new Runnable() {
    @Override
    public void run() {
    try {
    new Updater<Customer>() {
    @Override
    public Customer makeObjectForUpdate() throws Exception {
    Customer c = getById(1);
    //c.setName("name"+System.currentTimeMillis());
    int p = Integer.parseInt(c.getPassword());
    c.setPassword(String.valueOf((p+1)));
    System.out.println("Thread:"+Thread.currentThread().getId()+",reload," + c);
    return c;
    }
    }.update(dao);
    } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }).start();
        }
        } catch (Exception e) {
            throw new ServiceException(e);
        }
        
    }
注:dao对象是我们自己封装的数据操作载体,你可以用原生session都一个意思。
通过实现抽象方法的方式将你make对象的业务逻辑包装起来,和调用端依赖的外围参数,比如form都可以可见,在工具类里面当更新失败递归的时候,重新调用抽象实现,也就是你的业务逻辑。这样就确保万无一失了,小方法和大家分享一下。经过测试,效果非常满意。
posted on 2013-07-23 09:51 蟑螂也疯狂 阅读(1431) 评论(1)  编辑  收藏

Feedback

# re: hibernate乐观锁以及脏对象更新失败自动重试小工具 2013-07-23 09:59 mshow
虎子  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: