背景
項目中使用了paoding-rose作為開發(fā)框架凹耙,該框架作為國產(chǎn)的一個十分優(yōu)秀的框架,在Jade方面處理的也非常好肠仪,但是在實際的使用過程中肖抱,發(fā)現(xiàn)了一個很有意思的問題,在使用Delete SQL語句批量刪除數(shù)據(jù)時异旧,當傳參進去只有一個參數(shù)時意述,會因為返回類型的不知道導致拋異常。
這里還是先把解決方案說了好了,升級到最新2.0以上版本即可(我們使用的是比較老的1.2.2版本)
然后下面為了備忘荤崇,特別記錄一下這個蠻有意思的問題拌屏。
Git上可以參考這個Issue
問題描述
問題主要體現(xiàn)為,偶然性會報ClassCastException錯誤术荤,具體表現(xiàn)形式如下:
java.lang.ClassCastException: [I cannot be cast to java.lang.Integer
at com.sun.proxy.$Proxy51.deleteRecommendListByIDs(Unknown Source)
at com.xx.service.xxxService.generateDefaultRecommendList(xxxService.java:214)
at com.xx.service.xxxService.main(xxxService.java:951)
其錯誤來源于通過@SQL的注解實現(xiàn)了SQL語句的執(zhí)行地方倚喂,具體的SQL如下:
@SQL(" DELETE FROM videolist_home_recommend "
+" WHERE ID IN ( :delRecords ) ")
public Integer deleteRecommendListByIDs(@SQLParam("delRecords") List<Integer> delRecords);
以上就是這個錯誤產(chǎn)生的基本現(xiàn)象,但是有意思的該問題并非一定會產(chǎn)生瓣戚,根據(jù)詳細的測試和分析發(fā)現(xiàn)端圈,只有在傳參List中元素個數(shù)大于1個以上時,會導致該錯誤的發(fā)生子库。
原因分析
- 首先分析了這個現(xiàn)象以后覺得可能是傳參進入的問題舱权,導致了List轉化存在的問題,但是后來請教了同事仑嗅,其實rose這里和數(shù)據(jù)庫中一般SQL語句的返回是不一致的宴倍,數(shù)據(jù)庫中返回的是影響的行數(shù),但是Rose中是一個是否更新成功的int[]仓技,其中1代表處理成功啊楚,0代表處理失敗(具體為什么這么處理后面會分析到)浑彰。還有這里有個需要反省的就是恭理,對getName()這個方法不熟悉,其實JVM已經(jīng)告訴了我們問題點郭变,出問題的類型是"[I"颜价,只不過沒反應過來,括號哭~
String java.lang.Class.getName()
Returns the name of the entity (class, interface, array class, primitive type, or void) represented by this Class object, as a String.
……
If this class object represents a class of arrays, then the internal form of the name consists of the name of the element type preceded by one or more '[' characters representing the depth of the array nesting. The encoding of element type names is as follows:
……
Element Type
boolean ??Z
byte ???B
……
int????I
long???J
short ???S
所以之后根據(jù)這個返回值將SQL方法的返回值替換成為int[]類型诉濒,然而有趣的是這次反而在參數(shù)list只有一個值時報錯了周伦,于是進一步debug到源碼中去看了一下(由于github上找不到1.2.2版本的內(nèi)容,又找了好久source文件)
這里就發(fā)現(xiàn)rose jade中關于這個處理很有意思:
- 在rose中未荒,SQL語句只有兩種類型:READ和WRITE专挪,其中show、select等查詢類的屬于READ類型片排,而update寨腔、delete等則屬于WRITE類型。
- 在WRITE類型中率寡,會將ID IN (:list) 這種語句自動翻譯成Batch語句來處理迫卢。但是在轉換成Batch來處理時,在傳入的list中只有一個值時冶共,又會根據(jù)參數(shù)個數(shù)乾蛤,將Batch轉化成了SingleBatch每界,因此造成了這種不統(tǒng)一的情況(兩者的返回值不一樣,一個是Integer家卖,一個是int[])
@Override
public Object execute(SQLType sqlType, StatementRuntime... runtimes) {
switch (runtimes.length) {
case 0:
return 0;
case 1:
return executeSingle(runtimes[0], returnType);
default:
return executeBatch(runtimes);
}
}
- 因此對照著源碼又看了一下新版本眨层,發(fā)現(xiàn)已經(jīng)對executeBatch做了修正,增加了對一般數(shù)據(jù)庫SQL返回影響行數(shù)的支持(有趣的是上荡,也并沒有放棄之前對int[]的支持趴樱,雖然可以理解為向下兼容,但是個人覺得其實也是想保持這種有趣的對數(shù)據(jù)處理的理解吧榛臼,挺有意思的)
- 1.2.2的版本
private Object executeBatch(StatementRuntime... runtimes) {
int[] updatedArray = new int[runtimes.length];
Map<String, List<StatementRuntime>> batchs = new HashMap<String, List<StatementRuntime>>();
for (int i = 0; i < runtimes.length; i++) {
StatementRuntime runtime = runtimes[i];
List<StatementRuntime> batch = batchs.get(runtime.getSQL());
if (batch == null) {
batch = new LinkedList<StatementRuntime>();
batchs.put(runtime.getSQL(), batch);
}
runtime.setProperty("_index_at_batch_", i); // 該runtime在batch中的位置
batch.add(runtime);
}
// TODO: 多個真正的batch可以考慮并行執(zhí)行~待定
for (Map.Entry<String, List<StatementRuntime>> batch : batchs.entrySet()) {
String sql = batch.getKey();
List<StatementRuntime> batchRuntimes = batch.getValue();
StatementRuntime runtime = batchRuntimes.get(0);
DataAccess dataAccess = dataAccessProvider.getDataAccess(//
runtime.getMetaData(), runtime.getProperties());
List<Object[]> argsList = new ArrayList<Object[]>(batchRuntimes.size());
for (StatementRuntime batchRuntime : batchRuntimes) {
argsList.add(batchRuntime.getArgs());
}
int[] batchResult = dataAccess.batchUpdate(sql, argsList);
if (batchs.size() == 1) {
updatedArray = batchResult;
} else {
int index_at_sub_batch = 0;
for (StatementRuntime batchRuntime : batchRuntimes) {
Integer _index_at_batch_ = batchRuntime.getProperty("_index_at_batch_");
updatedArray[_index_at_batch_] = batchResult[index_at_sub_batch++];
}
}
}
return updatedArray;
}
- 2.0u8的版本
private Object executeBatch(StatementRuntime... runtimes) {
int[] updatedArray = new int[runtimes.length];
Map<String, List<StatementRuntime>> batchs = new HashMap<String, List<StatementRuntime>>();
for (int i = 0; i < runtimes.length; i++) {
StatementRuntime runtime = runtimes[i];
List<StatementRuntime> batch = batchs.get(runtime.getSQL());
if (batch == null) {
batch = new ArrayList<StatementRuntime>(runtimes.length);
batchs.put(runtime.getSQL(), batch);
}
runtime.setAttribute("_index_at_batch_", i); // 該runtime在batch中的位置
batch.add(runtime);
}
// TODO: 多個真正的batch可以考慮并行執(zhí)行(而非順序執(zhí)行)~待定
for (Map.Entry<String, List<StatementRuntime>> batch : batchs.entrySet()) {
String sql = batch.getKey();
List<StatementRuntime> batchRuntimes = batch.getValue();
StatementRuntime runtime = batchRuntimes.get(0);
DataAccess dataAccess = dataAccessFactory.getDataAccess(//
runtime.getMetaData(), runtime.getAttributes());
List<Object[]> argsList = new ArrayList<Object[]>(batchRuntimes.size());
for (StatementRuntime batchRuntime : batchRuntimes) {
argsList.add(batchRuntime.getArgs());
}
int[] batchResult = dataAccess.batchUpdate(sql, argsList);
if (batchs.size() == 1) {
updatedArray = batchResult;
} else {
int index_at_sub_batch = 0;
for (StatementRuntime batchRuntime : batchRuntimes) {
Integer _index_at_batch_ = batchRuntime.getAttribute("_index_at_batch_");
updatedArray[_index_at_batch_] = batchResult[index_at_sub_batch++];
}
}
}
if (returnType == void.class) {
return null;
}
if (returnType == int[].class) {
return updatedArray;
}
if (returnType == Integer.class || returnType == Boolean.class) {
int updated = 0;
for (int value : updatedArray) {
updated += value;
}
return returnType == Boolean.class ? updated > 0 : updated;
}
throw new InvalidDataAccessApiUsageException(
"bad return type for batch update: " + runtimes[0].getMetaData().getMethod());
}
小結
雖然可以理解為是框架本身的一個問題,但是發(fā)現(xiàn)對本身框架使用中的一些細節(jié)了解的不夠深刻窜司,已經(jīng)對一些不太常見但很有用的信息熟悉不足沛善,導致了還是花費了一些時間在這個問題上。
但總體來說還是蠻有意思的一個問題塞祈,一是記錄下來給自己做一個回顧金刁,二是如果能幫助到遇到同樣問題的同學就太好了~