下午写了一个DO类,里面有一个枚举的对象,因此用到了ibatis的自定义handler
Account类里的status属性是枚举类型,对应的xml文件如下:
............
<typeAlias alias="account" type="test.Account"/>
<insert id="insertAccount" parameterClass="account">
insert into ACCOUNT (
ACC_ID,
ACC_FIRST_NAME,
ACC_LAST_NAME,
STATUS)
values (
#id#,
#firstName#,
#lastName#,
#status,handler=test.MyHandler#
)
</insert>
.................
上面的status是枚举类,所以在总的配置文件sqlmapconfig.xml中配置了handler
......
<typeHandler javaType="test.Account" jdbcType="VARCHAR" callback="test.MyHandler"/>
........
以上这么配置,是没有问题的,项目里面的handler也都是这么配置,以前没有仔细深究,今天仔细看了看。有个疑问:
为什么要配置2处?有这个必要么?
于是乎,回来翻了一下ibatis的源代码,释然了。
首先看看ibatis解析xml文件的代码:
com.ibatis.sqlmap.engine.mapping.parameter.InlineParameterMapParser
public SqlText parseInlineParameterMap(TypeHandlerFactory typeHandlerFactory, String sqlStatement, Class parameterClass) {
.....
StringTokenizer parser = new StringTokenizer(sqlStatement, PARAMETER_TOKEN, true);
StringBuffer newSqlBuffer = new StringBuffer();
//这个while循环把#aaa#,#bbb#,#ccc#这种东西替换成 ?,?,?,并解析##之间的内容
while (parser.hasMoreTokens()) {
token = parser.nextToken();
if (PARAMETER_TOKEN.equals(lastToken)) {
if (PARAMETER_TOKEN.equals(token)) {
newSqlBuffer.append(PARAMETER_TOKEN);
token = null;
} else {
ParameterMapping mapping = null;
//PARAM_DELIM=":"
if (token.indexOf(PARAM_DELIM) > -1) {
mapping = oldParseMapping(token, parameterClass, typeHandlerFactory);
} else {
//这里面解析##之间的内容,返回一个mapping
mapping = newParseMapping(token, parameterClass, typeHandlerFactory);
}
mappingList.add(mapping);
newSqlBuffer.append("?");
boolean hasMoreTokens = parser.hasMoreTokens();
if (hasMoreTokens)
token = parser.nextToken();
if (!hasMoreTokens || !PARAMETER_TOKEN.equals(token)) {
throw new SqlMapException(
"Unterminated inline parameter in mapped statement near '"
+ newSqlBuffer.toString() + "'");
}
token = null;
}
} else {
if (!PARAMETER_TOKEN.equals(token)) {
newSqlBuffer.append(token);
}
}
lastToken = token;
}
....
}
迫不及待的进入到newParseMapping方法中
一大堆的if..else,慢慢看来
private ParameterMapping newParseMapping(String token, Class parameterClass, TypeHandlerFactory typeHandlerFactory) {
ParameterMapping mapping = new ParameterMapping();
/*同样是用StringTokenizer,不过这里的分割符号为"="或者","
*所以如果##之间的字符串是
*propertyName,javaType=string,jdbcType=VARCHAR,mode=IN,nullValue=N/A,handler=string,numericScale=2
那么解析出来就是propertyName javaType string jdbcType ......
*/
StringTokenizer paramParser = new StringTokenizer(token, "=,", false);
//第一个显然是参数的名字
mapping.setPropertyName(paramParser.nextToken());
while (paramParser.hasMoreTokens()) {
String field = paramParser.nextToken();
if (paramParser.hasMoreTokens()) {
String value = paramParser.nextToken();
if ("javaType".equals(field)) {
value = typeHandlerFactory.resolveAlias(value);
mapping.setJavaTypeName(value);
} else if ("jdbcType".equals(field)) {
mapping.setJdbcTypeName(value);
} else if ("mode".equals(field)) {
mapping.setMode(value);
} else if ("nullValue".equals(field)) {
mapping.setNullValue(value);
} else if ("handler".equals(field)) {
try {
//到了handler这一快,在例子中,我们指定了handler
//所以,这里直接通过反射,给我们造了一个出来。
//由此可见,如果##之间如果有配handler,则会优先用这个,外面定义的handler在这里不起任何作用
//如果我们没有在这里配置handler呢?接着往下看
value = typeHandlerFactory.resolveAlias(value);
Object impl = Resources.instantiate(value);
if (impl instanceof TypeHandlerCallback) {
mapping.setTypeHandler(new CustomTypeHandler((TypeHandlerCallback) impl));
} else if (impl instanceof TypeHandler) {
mapping.setTypeHandler((TypeHandler) impl);
} else {
throw new SqlMapException ("The class " + value + " is not a valid implementation of TypeHandler or TypeHandlerCallback");
}
} catch (Exception e) {
throw new SqlMapException("Error loading class specified by handler field in " + token + ". Cause: " + e, e);
}
} else if ("numericScale".equals(field)) {
try {
Integer numericScale = Integer.valueOf(value);
if (numericScale.intValue() < 0) {
throw new SqlMapException("Value specified for numericScale must be greater than or equal to zero");
}
mapping.setNumericScale(numericScale);
} catch (NumberFormatException e) {
throw new SqlMapException("Value specified for numericScale is not a valid Integer");
}
} else {
throw new SqlMapException("Unrecognized parameter mapping field: '" + field + "' in " + token);
}
} else {
throw new SqlMapException("Incorrect inline parameter map format (missmatched name=value pairs): " + token);
}
}
//如果没有配置handler,这里会根据parameterClass给一个UnkownTypeHandler对象
//一般我们都会定义参数的parameterClass,所以关键看看那个else里面发生了什么。
if (mapping.getTypeHandler() == null) {
TypeHandler handler;
if (parameterClass == null) {
handler = typeHandlerFactory.getUnkownTypeHandler();
} else {
//又调用resolveTypeHandler
handler = resolveTypeHandler(typeHandlerFactory, parameterClass, mapping.getPropertyName(), mapping.getJavaTypeName(), mapping.getJdbcTypeName());
}
mapping.setTypeHandler(handler);
}
return mapping;
}
再看看这个方法:resolveTypeHandler
private TypeHandler resolveTypeHandler(TypeHandlerFactory typeHandlerFactory, Class clazz, String propertyName, String javaType, String jdbcType) {
TypeHandler handler = null;
if (clazz == null) {
// Unknown
handler = typeHandlerFactory.getUnkownTypeHandler();
} else if (DomTypeMarker.class.isAssignableFrom(clazz)) {
// DOM
handler = typeHandlerFactory.getTypeHandler(String.class, jdbcType);
} else if (java.util.Map.class.isAssignableFrom(clazz)) {
// Map
if (javaType == null) {
handler = typeHandlerFactory.getUnkownTypeHandler(); //BUG 1012591 - typeHandlerFactory.getTypeHandler(java.lang.Object.class, jdbcType);
} else {
try {
javaType = typeHandlerFactory.resolveAlias(javaType);
Class javaClass = Resources.classForName(javaType);
handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
} catch (Exception e) {
throw new SqlMapException("Error. Could not set TypeHandler. Cause: " + e, e);
}
}
} else if (typeHandlerFactory.getTypeHandler(clazz, jdbcType) != null) {
// Primitive
handler = typeHandlerFactory.getTypeHandler(clazz, jdbcType);
} else {
//关键在这里,根据配置,我们在##之间显然没有配置javatype,显然为空
if (javaType == null) {
//class是parameterclass,这里通过该class的get方法获取这个枚举类的class类型
Class type = PROBE.getPropertyTypeForGetter(clazz, propertyName);
//接着,根据这个class,和jdbcType去factory里面查,此时jdbcType是空的
handler = typeHandlerFactory.getTypeHandler(type, jdbcType);
} else {
try {
//如果配置了javaType,则根据class类型从factory直接获取之,factory后面会讲到
javaType = typeHandlerFactory.resolveAlias(javaType);
Class javaClass = Resources.classForName(javaType);
handler = typeHandlerFactory.getTypeHandler(javaClass, jdbcType);
} catch (Exception e) {
throw new SqlMapException("Error. Could not set TypeHandler. Cause: " + e, e);
}
}
}
return handler;
}
再来看看factory的代码,很简单:
public TypeHandler getTypeHandler(Class type, String jdbcType) {
/*这个typeHandlerMap里面包含了所有的typeHandler,如:
*基本对象的handler,还有我们自定义在statement外的handler
*factory其实就是一个Map<class,Map<jdbcType,Handler>>
*一个对象可以对应多种jdbcType的handler
*/
Map jdbcHandlerMap = (Map) typeHandlerMap.get(type);
TypeHandler handler = null;
//回到例子中来,我们通过class=test.MyHandler,显然能够找到一个Map<jdbcType,Handler>
if (jdbcHandlerMap != null) {
//接着,就杯具了。。。。
/*直接用jdbcType这个空的对象去map里面取,如果我们的type是ibatis内置的对象或者基本类型还好,它会再初始化的时候插入一个key=null,value=基本的handler(如:IntegerHandler,LongHandler) 的记录进去,因此,对于一些string,interger等属性,即使不写jdbctype,也是可以正常的获得handler的。而此时,我们的jdbcType是空的,由于枚举类是自定义的,故:对应的map里面没有放置key为null的默认handler,而是放置了一个key=VARCHAR,value为test.TypeHandler的东东。所以,取出来的handler为空
*/
handler = (TypeHandler) jdbcHandlerMap.get(jdbcType);
if (handler == null) {
//再取一次,也是徒劳
handler = (TypeHandler) jdbcHandlerMap.get(null);
}
}
//新版的ibatis增加了默认的枚举handler,如果是这样就没有问题了
//可惜公司用的ibatis是木有下面这几行代码的。
if (handler == null && type != null && Enum.class.isAssignableFrom(type)) {
handler = new EnumTypeHandler(type);
}
return handler;
}
提一下UnknownTypeHandler
上面提到的是非动态的sql,如果是动态的sql,则会在解析完xml文件后,所有的变量的handler都会设置成UnknownTypeHandler
它的代setParameter码如下:
public void setParameter(PreparedStatement ps, int i, Object parameter, String jdbcType)
throws SQLException {
/*
parameter是传递进来的变量,有可能是map里面的也可能是参数类里面的
jdbcType 是##之间的设置的属性,如:#status,jdbcType=VARCHAR#
*/
//直接读取参数的类型
Class searchClass = parameter.getClass();
这个默认是false
if ( usingJavaPre5 ) {
try {
searchClass = getBaseClass(searchClass);
}
catch ( Exception ex ) {
searchClass = null;
}
}
if ( searchClass == null ) {
searchClass = parameter.getClass();
}
//仍然是通过参数的class和jdbcType来获取handler
TypeHandler handler = factory.getTypeHandler(searchClass, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
总结:
- handler是通过javatype和jdbctype来寻找的,如果javatype为空,则自动通过parameterclass对象的get方法来获取。而jdbctype只能通过##之间的配置获取,如果这个为空,并且又没有在##之间指定handler,肯定找不到handler
- 在##之间指定的handler会优先使用,木有则回去factory里根据javatype和jdbctype获取。
- 在新版(相对于我的项目中的ibatis版本)的ibatis中,枚举类有默认的handler实现,可以不用写handler(这个没自己试过,只是源码里面有)
- 查询的statement根据resultmap里面的定义来调用handler
回到例子中:
如果在statement之外木有定义handler,则需要在##之间这些写
#status,handler=test.MyHandler#
如果定义了handler,则只需要增加一个jdbctype的属性,即可找到handler
显然,这种写法比上面的好,可以复用。
#status,jdbcType=VARCHAR#
注意:
对于接口类型的javatype,在配置文件里面的javatype最好写成实现类的class:
如有这么一个类
public class Account{
......
//这个字段使用自定义的handler来提交(设置返回)数据
private List<String> names;
......
//set方法
public void setNames(List<String> names){
this.names=names;
}
//get方法
public List<String> getNames(){
return names;
}
}
总的sqlmapconfig文件中,这么配置
.................
<!--这里也是用java.util.arrayList-->
<typeHandler jdbcType="VARCHAR" javaType="java.util.ArrayList" callback="aaa.bbb.cc.eee.ffff" />
.................
在对应的ibatis sql映射文件中,其javaType必须写成
<resultMap id="aaaa" class="aaa">
...........
<!-- 这里写成java.util.ArrayList 否则查询数据的时候会报错
找不到handle,导致空指针。因为对应的handlerFactory里面
class存的是ArrayList
-->
<result property="incomeTypes" column="aaa" jdbcType="VARCHAR" javaType="java.util.ArrayList" />
..........
<!-- 下面的一些statement 也配置成java.util.arrayList-->
<insert>
.......
#names,jdbcType=VARCHAR,javaType=java.util.ArrayList#,
....
</insert>