樂觀鎖是hibernate用來識別update到數(shù)據(jù)庫的對象是否是臟對象的功能鎖,在get->operation->update這種非原子操作中這種并發(fā)風險非常常見,get同一個對象,按照相反順序設置新值然后更新就會出現(xiàn)臟對象的覆蓋。
可以按照如下配置設置樂觀鎖version,這樣剛才的臟對象操作就會彈出 StaleObjectStateException 異常。配置如下(注解也可以):
可以按照如下配置設置樂觀鎖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的覆蓋就不行了,和業(yè)務邏輯違背。也就說你要知道要update的這個對象是怎么來的,中間做了什么,才得到的新值,這幾個小操作作為原子操作,在重試的時候要還原回當初的業(yè)務邏輯。于是我寫了個下面的小類:
在po和db里也要增加相應的字段。
不要以為配置這個就萬事大吉了,用10個線程操作更新同一個對象,幾乎有9-10個都會失敗,彈出異常,那怎么辦呢,就要處理,不處理還不如不加鎖呢,全失敗了。
如果update的新值是絕對值比如表單form也無所謂了,不加鎖覆蓋了也行,加鎖重試也是一樣效果,可是要是那種getValue,然后+1 或者+2,+3,再update的覆蓋就不行了,和業(yè)務邏輯違背。也就說你要知道要update的這個對象是怎么來的,中間做了什么,才得到的新值,這幾個小操作作為原子操作,在重試的時候要還原回當初的業(yè)務邏輯。于是我寫了個下面的小類:
/**
* 更新hibernate對象,用此類更新可以實現(xiàn)臟對象自動重試更新功能
* @author hongweizhang@cyou-inc.com
* @param <T>
*/
public abstract class Updater<T> {
private Logger logger = LoggerFactory.getLogger(Updater.class);
/**
* 構(gòu)建實際對象
*
* @return
* @throws Exception
*/
public abstract T makeObjectForUpdate() throws Exception;
/**
* 實際更新方法,已經(jīng)實現(xiàn)臟對象更新自動重試功能。
* @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) { //不怕一萬就怕萬一 死循環(huán)
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 {
}
}
/**
* 更新數(shù)據(jù)庫
* @param object
* @param session
* @throws Exception
*/
public boolean update(Dao dao) throws Exception{
return update(dao, 0);
}
}
使用方法調(diào)用端如下(多線程測試,實際使用去掉多線程):
通過實現(xiàn)抽象方法的方式將你make對象的業(yè)務邏輯包裝起來,和調(diào)用端依賴的外圍參數(shù),比如form都可以可見,在工具類里面當更新失敗遞歸的時候,重新調(diào)用抽象實現(xiàn),也就是你的業(yè)務邏輯。這樣就確保萬無一失了,小方法和大家分享一下。經(jīng)過測試,效果非常滿意。
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對象是我們自己封裝的數(shù)據(jù)操作載體,你可以用原生session都一個意思。通過實現(xiàn)抽象方法的方式將你make對象的業(yè)務邏輯包裝起來,和調(diào)用端依賴的外圍參數(shù),比如form都可以可見,在工具類里面當更新失敗遞歸的時候,重新調(diào)用抽象實現(xiàn),也就是你的業(yè)務邏輯。這樣就確保萬無一失了,小方法和大家分享一下。經(jīng)過測試,效果非常滿意。