官网上面讲到:无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。那么为什么会有类型处理器呢?这一点并不难理解,SQL语句事实上可以理解为一门面向数据库的编程语言。所以相对而言都有自己的数据类型。这也就意味着存在数据类型不一至的问题。同时不同的数据库之间数据类型还有一定的差义。类型处理器则就是用于处理数据类型不一至的问题。

笔者看过几个不同的ORM框架都存在着类型处理器的概念。可见类型处理器在ORM框架上实现有多么重要。在官网上面已经列出了20多种的内部类型处理器。笔者建议最好选择性的看。好比如说笔者当前的列子里面用到的一个叫UnknownTypeHandler的类型处理器。

List<Product> products = dao.SelectProducts(30, "a");
<select id="SelectProducts" resultMap="result" >
select * from Products where #{0} > ProductID and ProductName like #{1}
</select>

例子里面并没有指出是什么样子的JAVA类型。所以当然是UnknownTypeHandler类型了。即然这样子我们不烦设置一下他的类型在来看看。我们只要把上面的配置修改一下就可以了。如下红色标记。

<select id="SelectProducts" resultMap="result" >
select * from Products where #{0,javaType=int,jdbcType=NUMERIC} > ProductID and ProductName like #{1,javaType=String,jdbcType=VARCHAR}
</select>

关于#{}的语法问题,相信笔者不用多讲大家都清楚。从上面简单的设置里,我们可以看到源码里面会找到IntegerTypeHandler和StringTypeHandler类型处理器来设置参数。类型处理器都是实现于TypeHandler接口,源码都存放在org.apache.ibatis.type的命名空间下。TypeHandler接口的源码也很简单。我们可以看到setParameter方法。顾名思义他就是用于设置参数。其他的方法都是用于处理回返的结果。如下。

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

想要了解源码,就要找一个合适的入切类——笔者选择IntegerTypeHandler类。IntegerTypeHandler类并没有直接的实现了TypeHandler接口。而是继承了BaseTypeHandler类。正如上面所讲的,BaseTypeHandler类的setParameter方法我们必须了解一下。如下

 public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
"Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
"Try setting a different JdbcType for this parameter or a different configuration property. " +
"Cause: " + e, e);
}
}
}

源码的意思很简单——把参数分为非空值和空值来分别设置。空值的设置方法是JDBC内部自己的。而非空值的方法是mybatis自己封装的。所以我们可以看到setNonNullParameter方法是一个抽象方法。看样子我们可以猜测的出来——真正的实现是放在子类的setNonNullParameter方法里面。

 public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

来看一下IntegerTypeHandler类的setNonNullParameter方法。

  public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}

事实上笔者认为IntegerTypeHandler类没有什么可看的。但是UnknownTypeHandler类的处理却得我们去研究。我们可以先想一下——不知道类型的情况下,如何知道需要的类型处理器。

 public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}

不难看出他又根据参数值和JDBC数据类型来判断获得相应的类型处理器。然后在进行参数设值。所以我们的目标移向resolveTypeHandler方法。

   private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<? extends Object> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}

源码的意思是判断传入的参数值是不是为空的,空则调用ObjectTypeHandler类型处器。如果不为空,根据参数传入的类型和当前JDBC数据类型来获得相应的类型处理器。如果还是找不到或是还是UnknownTypeHandler类,则调用ObjectTypeHandler类型处器。UnknownTypeHandler类实际没有设置参数值的功能。而是由其他的类型处理器来实现。

好了,到了这里面对于类型处理器,相信大家都有了一定的理解。但是这里还有一个问题值得我们去研究一下——类型处理器是什么时候创建的。想要了解他的来源就必须回头去看一下DefaultParameterHandler类的setParameters方法。也就是上一章的结尾处相同的代码。

 public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}

BoundSql类上一章笔者也有讲到。他是用于存放sql语句信息。为什么这里要提到他呢?相信从源码里面我们可以看到类型处理器是通过ParameterMapping类来获得的。而ParameterMapping类又是通过 BoundSql类获得。所以就不得不去了解一下BoundSql类是如何获得ParameterMapping类的。

关于如何BoundSql类是如何获得ParameterMapping类的。事实这一块跟XMLMapperBuilder类有关系。XMLMapperBuilder类就是用于加载Mapper文件的。而他的configurationElement方法。正是我们现在需要了解的。如下

 private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

最后面的buildStatementFromContext方法是了解如何获得ParameterMapping类的开头。这句代码中已经很明显的看出他是用于加载select,insert等节点信息。当我们继续跟进去的话,最终会看到一个了parseStatementNode方法。这一个过程读者们自行查看。

  processSelectKeyNodes(id, parameterTypeClass, langDriver);

     // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");

笔者只是贴出一部分的代码。红色标记的内容是我们所要关注的重点。(其外parseStatementNode方法还是了解如何创建MappedStatement类的入口类。)笔者为什么要提到SqlSource接口呢?我们都知道在mybatis框架里面的sql语句决不可能只有sql语句。还会出现if之类的语法。那么这种sql语句,mybatis框架叫他为动态 SQL。而BoundSql类就是通过SqlSource接口所继承的类获得。

mybatis对sql语句定义为三种半。为什么是三种半呢?因为还有一个可能正在研发中。每一种在源码中都有相关的类。

1.DynamicSqlSource类用于动态SQL。

2.StaticSqlSource类用于静态SQL。

3.ProviderSqlSource类用于脚本语言。

4.VelocitySqlSource类用于XXX。还是读者们自己去看吧。

SqlSource接口的源码才是笔者为什么要讲SqlSource接口的最好证明。如下

public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

我们搞清楚了BoundSql类是哪里来了之后。我们在去了解如何获得ParameterMapping类就变得简单多了。这里笔者就不把全部的过程拿出来讲了。就直接把获得ParameterMapping类的始点提出来——SqlSourceBuilder类的parse方法里面。

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql = parser.parse(originalSql);
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}

ParameterMappingTokenHandler类就是用于解析ParameterMapping类的。具体如何去解析不用笔者在动手吧。在建新ParameterMapping类的时候,他的内部resolveTypeHandler方法会被调用。而这个时候我们的类型处理器正式出现了。

 private void resolveTypeHandler() {
if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
Configuration configuration = parameterMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
}
}

类型处理器相关的内容。到了这里可以说是结束了。我们知道了他的来源,也知道了他的去处。事实如果我们认真点去了解正个过程。你们会发现这个过程用于很buidler设计模式。同时mybatis框架把mapper文件的信息分为mapper根处理、四节点处理、脚本处理、SQL语句处理。

1.Mapper根处理对应的XMLMapperBuilder类。

2.四节点处理对应的XMLStatementBuilder类。即是select节点,update节点,insert节点,delect节点。

3.脚本处理对应的XMLScriptBuilder类。

4.SQL语句处理对应的SqlSourceBuilder类。

当然这也是笔者自己的看法而以。不一定对。你们可以自己去了解。

MyBatis 源码分析——类型处理器的更多相关文章

  1. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

  2. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  3. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  4. (一) Mybatis源码分析-解析器模块

    Mybatis源码分析-解析器模块 原创-转载请说明出处 1. 解析器模块的作用 对XPath进行封装,为mybatis-config.xml配置文件以及映射文件提供支持 为处理动态 SQL 语句中的 ...

  5. 精尽 MyBatis 源码分析 - 基础支持层

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  6. 精尽 MyBatis 源码分析 - MyBatis 初始化(一)之加载 mybatis-config.xml

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  7. 精尽 MyBatis 源码分析 - MyBatis 初始化(三)之 SQL 初始化(上)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  8. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. Python3基础 闭包 简单示例

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

  2. MySQL动态添删改列字段

    动态增加列字段: ); 动态删除列字段: alter table TableName drop column field_id; 动态修改列字段: alter table TableName chan ...

  3. 兼容ie6及以上的表格行滑过时背景色切换的效果

    ;(function(window){    var tb = document.getElementById('tablelist');    function trfocus(){//为了兼容IE ...

  4. Selenium2(java)selenium常用API 四

    WebElement相关方法 1.点击操作 WebElement button = driver.findElement(By.id("login")); button.click ...

  5. CentOS 6.6下JDK1.7安装与配置(Linux)经典入门详解案例

    最近用的linux较多,在网站找了一些关于linux环境下jdk安装的教程,过程是有的但是好多细节都没有表现出来,所以我花了点时间总结了一下,希望对大家都有帮助... CentOS下JDK1.7安装与 ...

  6. linux下两台服务器文件实时同步方案设计和实现

    inux下两台服务器文件实时同步方案设计和实现 假设有如下需求: 假设两个服务器: 192.168.0.1 源服务器  有目录 /opt/test/ 192.168.0.2 目标服务器  有目录 /o ...

  7. 解析STL中典型的内存分配

    1 vector 在C++中使用vector应该是非常频繁的,但是你是否知道vector在计算内存分配是如何么? 在c++中vector是非常类似数组,但是他比数组更加灵活,这就表现在他的大小是可以自 ...

  8. C# winform 按钮设置左边图标

    按钮设置操作如下: 1.先设置按钮图标,再设置图标的对齐方式 2.再设置按钮字体的对齐方式. 3.最后设置排列: TextImageRelation:设置为ImageBeforeText

  9. python bottle 简介

    bottle是一个轻量级的python web框架, 可以适配各种web服务器,包括python自带的wsgiref(默认),gevent, cherrypy,gunicorn等等.bottle是单文 ...

  10. iOS 插件化开发汇总 Small框架

    应用插件化背景 目前很多应用功能越来越多,软件显得越来越臃肿.因此插件化就成了很多软件发展的必经之路,比如支付宝这种平台级别的软件: 页上密密麻麻的功能,而且还在增多,照这个趋势发展下去,软件包的大小 ...