`
aids198311
  • 浏览: 58820 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

ibatis如何自动获取自定义的handler

阅读更多
下午写了一个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>




分享到:
评论
2 楼 顾惜朝 2013-07-17  
1 楼 Singal 2011-08-18  
写的特别好,潜水这么多年,我在javaeye(iteye)的第一次就给你了!!!

相关推荐

Global site tag (gtag.js) - Google Analytics