今天遇到一個問題,數據庫用的oracle,先簡單描述一下,如果沒有設置參數的jdbcType,Mybatis不能將null值設置爲待執行sql語句的參數值,再詳細描述一下發現問題的經過:
之前用Mybatis時,在xml文件中寫SQL的時候,常會用到<if test="">這樣的判斷條件,如下:
一大堆,之前對<if>標籤的理解本質上就是看條件拼SQL,條件成立了就拼上這一句,不符合條件就不要這句了SQL還短一點。其實不只是這樣,如果用oracle的ojdbc包,不寫這個條件判斷可能會拋出異常。
是這樣的,有個場景 ,需要更新字段的值,如下:
當時想的是字段不加這個<if test="">,如果傳進來的是null那就直接更新爲null唄,還特意去數據庫試了一下update XXX set XXX = null是可以執行的,然後開開心心的一跑居然拋異常了,異常如下,只截取Cause By部分:
通過異常線程棧信息可以定位到最先拋出異常的地方時OracleStatement.getInternalType()這個方法,而且這個異常信息“無效的列類型1111”似乎有點常見,那就找到這個方法看看,這個方法裏主要是switch帶了一長串的case,不方便截圖直接省略着寫出來:
int getInternalType(int var1) throws SQLException {
boolean var2 = false;
short var4;
switch(var1) {
case -104:
var4 = 183;
break;
case -103:
var4 = 182;
break;
case -102:
var4 = 231;
break;
case -101:
var4 = 181;
break;
case -100:
case 93:
var4 = 180;
break;
case -16:
case -1:
var4 = 8;
break;
case -15:
case -9:
case 12:
var4 = 1;
break;
case -14:
var4 = 998;
break;
case -13:
var4 = 114;
break;
case -10:
var4 = 102;
break;
case -8:
var4 = 104;
break;
case -7:
case -6:
case -5:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
var4 = 6;
break;
case -4:
var4 = 24;
break;
case -3:
case -2:
var4 = 23;
break;
case 0:
var4 = 995;
break;
case 1:
var4 = 96;
break;
case 70:
var4 = 1;
break;
case 91:
case 92:
var4 = 12;
break;
case 100:
var4 = 100;
break;
case 101:
var4 = 101;
break;
case 999:
var4 = 999;
break;
case 2002:
case 2003:
case 2007:
case 2008:
var4 = 109;
break;
case 2004:
var4 = 113;
break;
case 2005:
case 2011:
var4 = 112;
break;
case 2006:
var4 = 111;
break;
default:
SQLException var3 = DatabaseError.createSqlException(this.getConnectionDuringExceptionHandling(), 4, Integer.toString(var1));
var3.fillInStackTrace();
throw var3;
}
return var4;
}
看來是var1沒有匹配上case,在最後的default裏面拋出了異常,那得知道var1是什麼,從異常的線程棧信息裏面可以看到是OraclePreparedStatement.setNullCritical()這個方法裏調用了這個getInternalType,那就去看看這個setNullCritical()方法:
這個方法也是一長串的case,不過我不關心下面是什麼,因爲在getInternalType()方法調用這個就已經拋異常了,我只想知道剛剛的var1是什麼,也就是這個方法裏的var2,繼續看異常棧信息往上找:
可以看到到BaseTypeHandler.setParameter()方法,裏面部分截圖是這樣的:
看調用setNull的地方可以知道我們想要的值就是這個jdbcType.TYPE_CODE,繼續往上找,可以看到DefaultParameterHandler.setParameters()方法裏面調用了這個setParameter()方法,已經到頭了,這個方法得截圖全一點:
這裏的jdbcType 就是我們在XML中寫SQL的時候給字段參數指定的,如下:
這樣這兩個參數的jdbcType就被指定成了VARCHAR,如果不指定的話就默認爲null,關鍵來了,看上圖紅圈中的這句:
如果我們不指定jdbcType,默認爲null,同時參數值又爲null,這不就是文章一開始說的場景嗎?看看這個getJdbcTypeForNull()給了一個什麼jdbcType,最後導致匹配不上case拋異常,這個方法:
就這?往上找找這個 jdbcTypeForNull,發現這個變量被初始化成了這個:
但同時又有一個set方法:
看來oracle給我機會改這個 jdbcTypeForNull了,可是我沒有珍惜,所以最後是因爲jdbcType是OTHER類型導致最後的異常,
點了點這個JdbcType.OTHER果然看到了這個“1111”:
這些從頭到尾都理清楚了,是因爲這條SQL語句我傳的參數值爲null,同時又沒有給參數設置jdbcType,oracle默認給了一個OTHER的jdbcType,最後導致在getInternalType()方法一長串的case裏沒有匹配上“1111”,看了一眼還真沒有,最後拋出了異常。
解決辦法也很多,要麼給參數設置jdbcType,要麼加<if test="xxx != null">條件,如果是參數值是null的話就不要設置參數了。以前對Mybatie的這兩個地方都是知其然不知其所以然,今天掃乾淨了,不過這是在oracle的jar包裏,其他數據庫不知道怎麼樣,有待去看一看。