今天遇到一个问题,数据库用的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包里,其他数据库不知道怎么样,有待去看一看。