好久沒寫Blog,最近事多,人忙。現在有空閒下來說說自己在最近一次jpa問題解決時候的實例。
給出出問題的代碼的簡單實現如下:
CacheService
@Autowired
private DistributedMap<ID,T> cache;
@Autowired
private PersistenceService service;
public void get(ID id){
T t = cache.get(id);
if(t != null){
return t;
} else {
t = service.loadForCache(ID);
cache.put(id,t);
return t;
}
}
Persistent Layer
public class PersistenceService {
public T loadForCache(ID id){
Query query = createQuery("select t from T where t.id=?");
query.setFetchPlan("detail");
...
}
...
}
@Entity
@FetchGroups({
@FetchGroup(name="detail", attributes={
@FetchAttribute(name="refs")
}),
...
})
public class T {
private ID id;
private Map<ID, A> refs = new HashMap<ID,A>();
...
...
}
Scheduled task:
@Autowired
private CacheService cache;
@Transactional
public void dailyProcess(){
T t = cache.get(id);
......
}
系統使用了IBM Websphere cluster distributed map做爲集羣的緩存,get函數簡單的先從cache拿,拿不到再通過jpa從數據庫load數據,由於數據比較複雜,使用了fetchplan來獲取需要序列化的部分屬性,由於使用了fetchplan,所以不再需要使用transaction來load數據了。
最近我爲系統添加了一個新的scheduled task, 每分鐘執行一次,在測試中發現每天總能出現2,3次openjpa assertOPEN issue,而且每個節點都會出。每次報錯都是在cache.put(id,t)這一行,試圖序列化T對象出錯,報錯的trace stack可能是任何一個操作websphere cache的地方(系統中使用了多個websphere的cache)。打開openjpa的runtime trace level日誌,可以看出系統每次實際出錯都是在我新加的scheduled的任務執行時,之所以看起來每次錯誤日誌地方不一樣是因爲cache.put操作的實現可能是在另外一個線程中執行(Websphere daemon batch operation thread)。。。所以任何一個操作websphere cache的get/set都有可能導致報錯。
現在說說具體報錯原因,之前get方法不在事物中,openjpa實現會在get方法調用loadForCache的時候按照fetchplan要求取出所有需要的數據,而我新加的方法講get放進事物裏,那麼問題就出現了,由於openjpa的優化,fetchplan的獲取可能不在是即時的(t = service.loadForCache(ID);),而cache.put(id,t)操作的實現可能要在websphere的後臺線程裏面做處理。。。很明顯後臺線程處理的時候很可能數據還沒有load完,assertOpen異常就出現了。
這個教訓告訴我,一定要多去了解你要是用的第三方代碼,在事務處理的時候千萬要小心不要有代碼是提交事務相關信息去其他線程去處理的。。。
要解決這個問題有2個辦法,第一個,在loadForCache方法上加上@transactional(propagation=Propagation.REQUIRES_NEW),還有一個就是手動編寫transaction相關代碼,只把需要的代碼放入事務
@Autowired
private PlatformTransactionManager txManager;
public void dailyProcess(){
T t = cache.get(id);
TransactionDefinition def = new DefaultTransactionDefinition();
//def.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED);
//def.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus txStatus = txManager.getTransaction(def);
boolean result = false;
try {
result = service.process(t);
txManager.commit(txStatus);
} catch (Exception e) {
result = false;
txManager.rollback(txStatus);
LOGGER.error("Transfer Error!",e);
}
return result;
}