`
aids198311
  • 浏览: 59513 次
  • 性别: 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)的第一次就给你了!!!

相关推荐

    Ibatis jar

    此外,Ibatis还提供了TypeHandler机制,用于自定义Java类型与数据库类型的转换,解决一些特殊类型的数据处理问题。 与Hibernate相比,Ibatis的优势在于对SQL的直接控制,使得开发者可以充分利用SQL的功能,优化...

    ibatis.util包

    TypeHandler是Ibatis中处理Java类型和数据库类型之间转换的关键,`TypeHandlerRegistry`可以根据Java类型或JDBC类型找到对应的TypeHandler。 8. **Plugins**: 插件机制是Ibatis的一大特色,`ibatis.util.Plugins`类...

    ibatis配置

    Ibatis提供了一些预定义的TypeHandler,也可以自定义。 9. **事务管理**: Ibatis支持手动和自动两种事务管理模式。手动模式下,开发者需要自己控制事务的开启、提交和回滚;自动模式下,可以通过Spring等框架进行...

    iBATIS实战

    12.3.2 CacheController的放入、获取以及清除操作 223 12.3.3 注册CacheController以供使用 224 12.4 配置iBATIS不支持的DataSource 224 12.5 定制事务管理 225 12.5.1 理解TransactionConfig接口 226 12.5.2 理解...

    ibatis pdf 快速入门

    3. **TypeHandler**:自定义类型处理器,处理Java类型与数据库类型的转换。 4. **插件机制**:通过拦截器插件,可以对SqlSession、Executor等进行扩展。 5. **MyBatis Generator**:自动化工具,自动生成Mapper...

    Mybatis自定义typeHandle过程解析

    在自定义TypeHandler的过程中,我们需要创建一个新的类并实现Mybatis提供的`org.apache.ibatis.type.TypeHandler`接口。这个接口有两个核心方法:`setNonNullParameter`和`getNullableResult`,它们分别用于设置SQL...

    ibatis tutorial

    2. **SqlSession创建**:通过SqlSessionFactory获取SqlSession实例,SqlSession是执行SQL的上下文。 3. **执行SQL**:调用SqlSession的select/insert/update/delete方法,传入SQL ID和参数。 4. **结果映射**:...

    iBatis学习笔记

    iBatis,又被称为SqlMap,是一款开源的“半自动”对象关系映射(Object Relational Mapping, ORM)框架,它通过将SQL语句与Java代码进行解耦,简化了数据库操作。与全自动ORM框架如Hibernate相比,iBatis提供了更细...

    Struts Spring Ibatis 实现的增删改查

    - Struts 提供了 Exception Handler 来捕获和处理运行时异常,可以自定义异常处理类,实现特定的错误页面显示。 - Spring 支持全局异常处理,通过 AOP 注解或配置,可以统一处理所有异常。 7. **测试**: - 单元...

    mybatis自定义类型处理器TypehHandler示例详解

    默认情况下,MyBatis提供了内置的类型处理器(TypeHandler)来自动完成Java类型与JDBC类型之间的转换。然而,当我们遇到一些特定的需求,如数据加密、状态转换等,就需要自定义类型处理器来满足这些需求。本文将深入...

    MyBatis3用户指南

    4. **参数绑定**:MyBatis支持多种参数绑定方式,如简单的类型匹配、Map参数、POJO对象以及自定义TypeHandler。它可以自动处理参数到SQL占位符的映射,避免SQL注入问题。 5. **结果映射**:结果映射用于将查询结果...

    mybatis中实现枚举自动转换方法详解

    为了实现枚举的自动转换,我们需要自定义一个枚举类型处理器,继承`BaseTypeHandler`并覆盖其中的四个关键方法: 1. `setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws ...

    mybatis3.1.1 jar+api

    3. 参数映射:MyBatis自动处理参数的设置,包括简单的类型、复杂的对象和自定义的TypeHandler。同时,它可以使用`@Param`注解来指定参数名,提高代码的清晰度。 4. 结果集映射:MyBatis提供了自动的结果映射功能,...

    mybatis-jar.zip

    - **TypeHandler**: 自定义类型处理器,处理特定类型的转换。 ### 总结 Mybatis-jar.zip 包含了运行 Mybatis 应用所需的核心库,使得开发者可以方便地进行 SQL 查询和数据操作。通过理解 Mybatis 的核心组件、使用...

    mybatis-3.2.8jar包及其源码jar包

    - 可以自定义类型处理器,通过实现`org.apache.ibatis.type.TypeHandler`接口。 通过这个3.2.8版本的jar包及源码,开发者不仅可以了解MyBatis的基础用法,还可以深入研究其内部实现,提升对数据库操作的优化能力,...

    Spring中文帮助文档

    11.5.2. 使用SimpleJdbcInsert来获取自动生成的主键 11.5.3. 指定SimpleJdbcInsert所使用的字段 11.5.4. 使用SqlParameterSource提供参数值 11.5.5. 使用SimpleJdbcCall调用存储过程 11.5.6. 声明SimpleJdbcCall...

    mybatis-resource:mybatis源码阅读

    MyBatis提供了多种内置TypeHandler,同时也支持自定义TypeHandler。 10. **插件机制** MyBatis的插件机制允许用户在不修改源码的情况下,对Executor、StatementHandler、ParameterHandler、ResultSetHandler等关键...

    Spring API

    11.5.2. 使用SimpleJdbcInsert来获取自动生成的主键 11.5.3. 指定SimpleJdbcInsert所使用的字段 11.5.4. 使用SqlParameterSource提供参数值 11.5.5. 使用SimpleJdbcCall调用存储过程 11.5.6. 声明SimpleJdbcCall...

    spring chm文档

    5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. Spring的AOP代理 6.2. @...

    Spring-Reference_zh_CN(Spring中文参考手册)

    5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 5.4.2.1. 注册用户自定义的PropertyEditor 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 ...

Global site tag (gtag.js) - Google Analytics