mybatis3.x源码深度解析二、关键对象与sql执行过程

转载自:https://www.cnblogs.com/zhjh256/p/8512392.html

  • 3 关键对象总结与回顾
    • 3.1 SqlSource
    • 3.2 SqlNode
      • ChooseSqlNode
      • ForEachSqlNode
      • IfSqlNode
      • StaticTextSqlNode
      • TextSqlNode
      • VarDeclSqlNode
      • TrimSqlNode
      • SetSqlNode
      • WhereSqlNode
    • 3.3 BaseBuilder
    • 3.4 AdditionalParameter
    • 3.5 TypeHandler
    • 3.6 对象包装器工厂ObjectWrapperFactory
    • 3.7 MetaObject
    • 3.8 对象工厂ObjectFactory
    • 3.13 LanguageDriver
    • 3.14 ResultMap
    • 3.15 ResultMapping
    • 3.16 Discriminator
  • 4 SQL语句的执行流程
    • 4.1 传统JDBC用法
    • 4.2 mybatis执行SQL语句
      • 4.2.1 获取openSession
      • 4.2.2 sql语句执行方式一
        • mybatis结果集处理
        • selectMap实现
        • update/insert/delete实现
      • 4.2.3 SQL语句执行方式二 SqlSession.getMapper实现
    • 4.3 动态sql
    • 4.4 存储过程与函数调用实现
    • 4.5 mybatis事务实现
    • 4.6 缓存
  • 5 执行期主要类总结
    • 5.1 执行器Executor
      • 5.4.1 SIMPLE执行器
      • 5.4.2 REUSE执行器
      • 5.4.3 BATCH执行器
      • 5.4.4 缓存执行器CachingExecutor的实现
    • 5.2 参数处理器ParameterHandler
    • 5.3 语句处理器StatementHandler
    • 5.4 结果集处理器ResultSetHandler

 

3 关键对象总结与回顾

3.1 SqlSource

SqlSource是XML文件或者注解方法中映射语句的实现时表示,通过SqlSourceBuilder.parse()方法创建,SqlSourceBuilder中符号解析器将mybatis中的查询参数#{}转换为?,并记录了参数的顺序。它只有一个方法getBoundSql用于获取映射语句对象的各个组成部分,它的定义如下:

/**
 * Represents the content of a mapped statement read from an XML file or an annotation. 
 * It creates the SQL that will be passed to the database out of the input parameter received from the user.
 * 代表从XML文件或者注解读取的映射语句的内容,它创建的SQL会被传递给数据库。
 * @author Clinton Begin
 */
public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

根据SQL语句的类型不同,mybatis提供了多种SqlSource的具体实现,如下所示:
mybatis3.x源码深度解析二、关键对象与sql执行过程_第1张图片

  • StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。
  • RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,没有、等动态标签和${} SQL拼接,比动态SQL语句要快,因为不需要运行时解析SQL节点。
  • DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理、等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。
  • ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。

3.2 SqlNode

  SqlNode接口主要用来处理CRUD节点下的各类动态标签比如if、choose、for-each,对每个动态标签,mybatis都提供了对应的SqlNode实现,这些动态标签可以相互嵌套且实现上采用单向链表进行应用,这样后面如果需要增加其他动态标签,就只需要新增对应的SqlNode实现就能支持。mybatis使用OGNL表达式语言。对sqlNode的调用在SQL执行期间的DynamicSqlSource.getBoundSql()方法中,SQL执行过程我们后面会讲解。
  当前版本的SqlNode有下列实现:

  其中MixedSqlNode代表了所有具体SqlNode的集合,其他分别代表了一种类型的SqlNode。下面对每个SqlNode的实现做简单的分析:

ChooseSqlNode

public class ChooseSqlNode implements SqlNode {
  private final SqlNode defaultSqlNode;
  private final List ifSqlNodes;

  public ChooseSqlNode(List ifSqlNodes, SqlNode defaultSqlNode) {
    this.ifSqlNodes = ifSqlNodes;
    this.defaultSqlNode = defaultSqlNode;
  }

  @Override
  public boolean apply(DynamicContext context) {
    // 遍历所有when分支节点,只要遇到第一个为true就返回
    for (SqlNode sqlNode : ifSqlNodes) {
      if (sqlNode.apply(context)) {
        return true;
      }
    }
    // 全部when都为false时,走otherwise分支
    if (defaultSqlNode != null) {
      defaultSqlNode.apply(context);
      return true;
    }
    return false;
  }
}

ForEachSqlNode

public class ForEachSqlNode implements SqlNode {
  public static final String ITEM_PREFIX = "__frch_";

  private final ExpressionEvaluator evaluator;
  private final String collectionExpression;
  private final SqlNode contents;
  private final String open;
  private final String close;
  private final String separator;
  private final String item;
  private final String index;
  private final Configuration configuration;

  public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
    this.evaluator = new ExpressionEvaluator();
    this.collectionExpression = collectionExpression;
    this.contents = contents;
    this.open = open;
    this.close = close;
    this.separator = separator;
    this.index = index;
    this.item = item;
    this.configuration = configuration;
  }

  @Override
  public boolean apply(DynamicContext context) {
    Map bindings = context.getBindings();
    // 将Map/Array/List统一包装为迭代器接口
    final Iterable iterable = evaluator.evaluateIterable(collectionExpression, bindings);
    if (!iterable.iterator().hasNext()) {
      return true;
    }
    boolean first = true;
    applyOpen(context);
    int i = 0;
    // 遍历集合
    for (Object o : iterable) {
      DynamicContext oldContext = context;
      if (first || separator == null) {
        context = new PrefixedContext(context, "");
      } else {
        context = new PrefixedContext(context, separator);
      }
      int uniqueNumber = context.getUniqueNumber();
      // Issue #709 
      if (o instanceof Map.Entry) {  //Map条目处理
        @SuppressWarnings("unchecked") 
        Map.Entry mapEntry = (Map.Entry) o;
        applyIndex(context, mapEntry.getKey(), uniqueNumber);
        applyItem(context, mapEntry.getValue(), uniqueNumber);
      } else { // List条目处理
        applyIndex(context, i, uniqueNumber);
        applyItem(context, o, uniqueNumber);
      }
      // 子节点SqlNode处理,很重要的一个逻辑就是将#{item.XXX}转换为#{__frch_item_N.XXX},这样在JDBC设置参数的时候就能够找到对应的参数值了
      contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
      if (first) {
        first = !((PrefixedContext) context).isPrefixApplied();
      }
      context = oldContext;
      i++;
    }
    applyClose(context);
    context.getBindings().remove(item);
    context.getBindings().remove(index);
    return true;
  }

  private void applyIndex(DynamicContext context, Object o, int i) {
    if (index != null) {
      context.bind(index, o);
      context.bind(itemizeItem(index, i), o);
    }
  }

  private void applyItem(DynamicContext context, Object o, int i) {
    if (item != null) {
      context.bind(item, o);
      context.bind(itemizeItem(item, i), o);
    }
  }

  private void applyOpen(DynamicContext context) {
    if (open != null) {
      context.appendSql(open);
    }
  }

  private void applyClose(DynamicContext context) {
    if (close != null) {
      context.appendSql(close);
    }
  }

  private static String itemizeItem(String item, int i) {
    return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString();
  }

  private static class FilteredDynamicContext extends DynamicContext {
    private final DynamicContext delegate;
    private final int index;
    private final String itemIndex;
    private final String item;

    public FilteredDynamicContext(Configuration configuration,DynamicContext delegate, String itemIndex, String item, int i) {
      super(configuration, null);
      this.delegate = delegate;
      this.index = i;
      this.itemIndex = itemIndex;
      this.item = item;
    }

    @Override
    public Map getBindings() {
      return delegate.getBindings();
    }

    @Override
    public void bind(String name, Object value) {
      delegate.bind(name, value);
    }

    @Override
    public String getSql() {
      return delegate.getSql();
    }

    @Override
    public void appendSql(String sql) {
      GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
        @Override
        // 将#{item.XXX}转换为#{__frch_item_N.XXX}
        public String handleToken(String content) {
          String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
          if (itemIndex != null && newContent.equals(content)) {
            newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
          }
          return new StringBuilder("#{").append(newContent).append("}").toString();
        }
      });

      delegate.appendSql(parser.parse(sql));
    }

    @Override
    public int getUniqueNumber() {
      return delegate.getUniqueNumber();
    }

  }


  private class PrefixedContext extends DynamicContext {
    private final DynamicContext delegate;
    private final String prefix;
    private boolean prefixApplied;

    public PrefixedContext(DynamicContext delegate, String prefix) {
      super(configuration, null);
      this.delegate = delegate;
      this.prefix = prefix;
      this.prefixApplied = false;
    }

    public boolean isPrefixApplied() {
      return prefixApplied;
    }

    @Override
    public Map getBindings() {
      return delegate.getBindings();
    }

    @Override
    public void bind(String name, Object value) {
      delegate.bind(name, value);
    }

    @Override
    public void appendSql(String sql) {
      if (!prefixApplied && sql != null && sql.trim().length() > 0) {
        delegate.appendSql(prefix);
        prefixApplied = true;
      }
      delegate.appendSql(sql);
    }

    @Override
    public String getSql() {
      return delegate.getSql();
    }

    @Override
    public int getUniqueNumber() {
      return delegate.getUniqueNumber();
    }
  }

}

IfSqlNode

public class IfSqlNode implements SqlNode {
  private final ExpressionEvaluator evaluator;  //表达式执行器
  private final String test;    //条件表达式
  private final SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }
}

ExpressionEvaluator的定义如下:

public class ExpressionEvaluator {
  // 布尔表达式解析,对于返回值为数字的if表达式,0为假,非0为真
  public boolean evaluateBoolean(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value instanceof Boolean) {
      return (Boolean) value;
    }
    if (value instanceof Number) {
      return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
    }
    return value != null;
  }

   // 循环表达式解析,主要用于foreach标签
  public Iterable evaluateIterable(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value == null) {
      throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
    }
    if (value instanceof Iterable) {
      return (Iterable) value;
    }
    if (value.getClass().isArray()) {
        // the array may be primitive, so Arrays.asList() may throw
        // a ClassCastException (issue 209).  Do the work manually
        // Curse primitives! :) (JGB)
        int size = Array.getLength(value);
        List answer = new ArrayList();
        for (int i = 0; i < size; i++) {
            Object o = Array.get(value, i);
            answer.add(o);
        }
        return answer;
    }
    if (value instanceof Map) {
      return ((Map) value).entrySet();
    }
    throw new BuilderException("Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.");
  }
}

StaticTextSqlNode

  静态文本节点不做任何处理,直接将本文本节点的内容追加到已经解析了的SQL文本的后面。

public class StaticTextSqlNode implements SqlNode {
  private final String text;

  public StaticTextSqlNode(String text) {
    this.text = text;
  }

  @Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

}

TextSqlNode

  TextSqlNode主要是用来将${}转换为实际的参数值,并返回拼接后的SQL语句,为了防止SQL注入,可以通过标签来创建OGNL上下文变量。

public class TextSqlNode implements SqlNode {
  private final String text;
  private final Pattern injectionFilter;

  public TextSqlNode(String text) {
    this(text, null);
  }

  public TextSqlNode(String text, Pattern injectionFilter) {
    this.text = text;
    this.injectionFilter = injectionFilter;
  }

  public boolean isDynamic() {
    DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
    GenericTokenParser parser = createParser(checker);
    parser.parse(text);
    return checker.isDynamic();
  }

  @Override
  public boolean apply(DynamicContext context) {
    GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
    context.appendSql(parser.parse(text));
    return true;
  }

  private GenericTokenParser createParser(TokenHandler handler) {
    return new GenericTokenParser("${", "}", handler);
  }

  private static class BindingTokenParser implements TokenHandler {

    private DynamicContext context;
    private Pattern injectionFilter;

    public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
      this.context = context;
      this.injectionFilter = injectionFilter;
    }
    // 将${}中的值替换为查询参数中实际的值并返回,在StaticTextSqlNode中,#{}返回的是?
    @Override
    public String handleToken(String content) {
      Object parameter = context.getBindings().get("_parameter");
      if (parameter == null) {
        context.getBindings().put("value", null);
      } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
        context.getBindings().put("value", parameter);
      }
      Object value = OgnlCache.getValue(content, context.getBindings());
      String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
      checkInjection(srtValue);
      return srtValue;
    }

    private void checkInjection(String value) {
      if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
        throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
      }
    }
  }

  private static class DynamicCheckerTokenParser implements TokenHandler {

    private boolean isDynamic;

    public DynamicCheckerTokenParser() {
      // Prevent Synthetic Access
    }

    public boolean isDynamic() {
      return isDynamic;
    }

    @Override
    public String handleToken(String content) {
      this.isDynamic = true;
      return null;
    }
  }

}

VarDeclSqlNode

public class VarDeclSqlNode implements SqlNode {

  private final String name;
  private final String expression;

  public VarDeclSqlNode(String var, String exp) {
    name = var;
    expression = exp;
  }

  @Override
  public boolean apply(DynamicContext context) {
    final Object value = OgnlCache.getValue(expression, context.getBindings());
    // 直接将ognl表达式加到当前映射语句的上下文中,这样就可以直接获取到了
    context.bind(name, value);
    return true;
  }

}

  DynamicContext.bind方法的实现如下:

  private final ContextMap bindings;
  public void bind(String name, Object value) {
    bindings.put(name, value);
  }

TrimSqlNode

public class TrimSqlNode implements SqlNode {

  private final SqlNode contents;
  private final String prefix;
  private final String suffix;
  private final List prefixesToOverride;  // 要trim多个文本的话,|分隔即可
  private final List suffixesToOverride;  // 要trim多个文本的话,|分隔即可
  private final Configuration configuration;

  public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
    this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
  }

  protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List prefixesToOverride, String suffix, List suffixesToOverride) {
    this.contents = contents;
    this.prefix = prefix;
    this.prefixesToOverride = prefixesToOverride;
    this.suffix = suffix;
    this.suffixesToOverride = suffixesToOverride;
    this.configuration = configuration;
  }

  @Override
  public boolean apply(DynamicContext context) {
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    // trim节点只有在至少有一个子节点不为空的时候才有意义
    boolean result = contents.apply(filteredDynamicContext);
    // 所有子节点处理完成之后,filteredDynamicContext.delegate里面就包含解析后的静态SQL文本了,此时就可以处理前后的trim了
    filteredDynamicContext.applyAll();
    return result;
  }

  private static List parseOverrides(String overrides) {
    if (overrides != null) {
      final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
      final List list = new ArrayList(parser.countTokens());
      while (parser.hasMoreTokens()) {
        list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
      }
      return list;
    }
    return Collections.emptyList();
  }

  private class FilteredDynamicContext extends DynamicContext {
    private DynamicContext delegate;
    private boolean prefixApplied;
    private boolean suffixApplied;
    private StringBuilder sqlBuffer;

    public FilteredDynamicContext(DynamicContext delegate) {
      super(configuration, null);
      this.delegate = delegate;
      this.prefixApplied = false;
      this.suffixApplied = false;
      this.sqlBuffer = new StringBuilder();
    }

    public void applyAll() {
      sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
      String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
      if (trimmedUppercaseSql.length() > 0) {
        applyPrefix(sqlBuffer, trimmedUppercaseSql);
        applySuffix(sqlBuffer, trimmedUppercaseSql);
      }
      delegate.appendSql(sqlBuffer.toString());
    }

    @Override
    public Map getBindings() {
      return delegate.getBindings();
    }

    @Override
    public void bind(String name, Object value) {
      delegate.bind(name, value);
    }

    @Override
    public int getUniqueNumber() {
      return delegate.getUniqueNumber();
    }

    @Override
    public void appendSql(String sql) {
      sqlBuffer.append(sql);
    }

    @Override
    public String getSql() {
      return delegate.getSql();
    }
    // 处理前缀
    private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!prefixApplied) {
        prefixApplied = true;
        if (prefixesToOverride != null) {
          for (String toRemove : prefixesToOverride) {
            if (trimmedUppercaseSql.startsWith(toRemove)) {
              sql.delete(0, toRemove.trim().length());
              break;
            }
          }
        }
        if (prefix != null) {
          sql.insert(0, " ");
          sql.insert(0, prefix);
        }
      }
    }

    // 处理后缀
    private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
      if (!suffixApplied) {
        suffixApplied = true;
        if (suffixesToOverride != null) {
          for (String toRemove : suffixesToOverride) {
            if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
              int start = sql.length() - toRemove.trim().length();
              int end = sql.length();
              sql.delete(start, end);
              break;
            }
          }
        }
        if (suffix != null) {
          sql.append(" ");
          sql.append(suffix);
        }
      }
    }
  }
}

SetSqlNode

  SetSqlNode直接委托给TrimSqlNode处理。参见TrimSqlNode。

WhereSqlNode

  WhereSqlNode直接委托给TrimSqlNode处理。参见TrimSqlNode。

3.3 BaseBuilder

  从整个设计角度来说,BaseBuilder的目的是为了统一解析的使用,但在实现上却出入较大。首先,BaseBuilder是所有解析类的MapperBuilderAssistant、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder等的父类。如下所示:

  BaseBuilder中提供类型处理器、JDBC类型、结果集类型、别名等的解析,因为在mybatis配置文件、mapper文件解析、SQL映射语句解析、基于注解的mapper文件解析过程中,都会频繁的遇到类型处理相关的解析。但是BaseBuilder也没有定义需要子类实现的负责解析的抽象接口,虽然XMLMapperBuilder、XMLConfigBuilder的解析入口是parse方法,XMLStatementBuilder的入口是parseStatementNode,不仅如此,MapperBuilderAssistant继承了BaseBuilder,而不是MapperAnnotationBuilder,实际上MapperAnnotationBuilder才是解析Mapper接口的主控类。

  所以从实现上来说,BaseBuilder如果要作为具体Builder类的抽象父类,那就应该定义一个需要子类实现的parse接口,要么就用组合代替继承。

3.4 AdditionalParameter

  额外参数主要是维护一些在加载时无法确定的参数,比如标签中的参数在加载时就无法尽最大努力确定,必须通过运行时执行org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql()中的SqlNode.apply()才能确定真正要执行的SQL语句,以及额外参数。比如,对于下列的foreach语句,它的AdditionalParameter内容为:
{frch_index_0=0, item=2, frch_index_1=1, _parameter=org.mybatis.internal.example.pojo.UserReq@5ccddd20, index=1, frch_item_1=2, _databaseId=null, frch_item_0=1}
  其中_parameter和_databaseId在DynamicContext构造器中硬编码,其他值通过调用ForEachSqlNode.apply()计算得到。与此相对应,此时SQL语句在应用ForeachSqlNode之后,对参数名也进行重写,如下所示:

    select lfPartyId,author as authors,subject,comments,title,partyName from LfParty where partyName = #{partyName}

        AND partyName like #{partyName}


        and lfPartyId in
         (  
        #{__frch_item_0.prop}
         , 
        #{__frch_item_1}
         ) 

  然后通过SqlSourceBuilder.parse()调用ParameterMappingTokenHandler计算出该sql的ParameterMapping列表,最后构造出StaticSqlSource。

3.5 TypeHandler

  当MyBatis将一个Java对象作为输入/输出参数执行CRUD语句操作时,它会创建一个PreparedStatement对象,并且调用setXXX()为占位符设置相应的参数值。XXX可以是Int,String,Date等Java内置类型,或者用户自定义的类型。在实现上,MyBatis是通过使用类型处理器(type handler)来确定XXX是具体什么类型的。MyBatis对于下列类型使用内建的类型处理器:所有的基本数据类型、基本类型的包裹类型、byte[] 、java.util.Date、java.sql.Date、java,sql.Time、java.sql.Timestamp、java 枚举类型等。对于用户自定义的类型,我们可以创建一个自定义的类型处理器。要创建自定义类型处理器,只要实现TypeHandler接口即可,TypeHandler接口的定义如下:

public interface TypeHandler {

  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;

}

  虽然我们可以直接实现TypeHandler接口,但是在实践中,我们一般选择继承BaseTypeHandler,BaseTypeHandler为TypeHandler提供了部分骨架代码,使得用户使用方便,几乎所有mybatis内置类型处理器都继承于BaseTypeHandler。下面我们实现一个最简单的自定义类型处理器MobileTypeHandler。

public class MobileTypeHandler extends BaseTypeHandler {  

    @Override  
    public Mobile getNullableResult(ResultSet rs, String columnName)  
            throws SQLException {  
        // mobile字段是VARCHAR类型,所以使用rs.getString
        return new Mobile(rs.getString(columnName));  
    }  

    @Override  
    public Mobile getNullableResult(ResultSet rs, int columnIndex)  
            throws SQLException {  
        return new Mobile(rs.getString(columnIndex));  
    }  

    @Override  
    public Mobile getNullableResult(CallableStatement cs, int columnIndex)  
            throws SQLException {  
        return new Mobile(cs.getString(columnIndex));  
    }  

    @Override  
    public void setNonNullParameter(PreparedStatement ps, int i,  
            Mobile param, JdbcType jdbcType) throws SQLException {  
        ps.setString(i, param.getFullNumber());  
    }  
}

  我们实现了自定义的类型处理器后,只要在mybatis配置文件mybatis-config.xml中注册就可以使用了,如下:

  
      

  上述完成之后,当我们在parameterType或者resultType或者resultMap中遇到Mobile类型的属性时,就会调用MobileTypeHandler进行代理出入参的设置和获取。

3.6 对象包装器工厂ObjectWrapperFactory

  ObjectWrapperFactory是一个对象包装器工厂,用于对返回的结果对象进行二次处理,它主要在org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue方法中创建对象的MetaObject时作为参数设置进去,这样MetaObject中的objectWrapper属性就可以被设置为我们自定义的ObjectWrapper实现而不是mybatis内置实现,如下所示:

private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) { // 如果有自定义的ObjectWrapperFactory,就不会总是返回false了,这样对于特定类就启用了的我们自定义的ObjectWrapper
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }

  典型的下划线转驼峰,我们就可以使用ObjectWrapperFactory来统一处理(当然,在实际中,我们一般不会这么做,而是通过设置mapUnderscoreToCamelCase来实现)。ObjectWrapperFactory 接口如下:

public interface ObjectWrapperFactory {

  boolean hasWrapperFor(Object object);

  ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);

}

  通过实现这个接口,可以判断当object是特定类型时,返回true,然后在下面的getWrapperFor中返回一个可以处理key为驼峰的ObjectWrapper 实现类即可。ObjectWrapper类可以说是对象反射信息的facade模式,它的定义如下:

public interface ObjectWrapper {

  Object get(PropertyTokenizer prop);

  void set(PropertyTokenizer prop, Object value);

  String findProperty(String name, boolean useCamelCaseMapping);

  String[] getGetterNames();

  String[] getSetterNames();

  Class getSetterType(String name);

  Class getGetterType(String name);

  boolean hasSetter(String name);

  boolean hasGetter(String name);

  MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);

  boolean isCollection();

  void add(Object element);

   void addAll(List element);

}

  当然,我们不需要从头实现ObjectWrapper接口,可以选择继承BeanWrapper或者MapWrapper。比如对于Map类型,我们可以继承MapWrapper,让参数useCamelCaseMapping起作用。MapWrapper默认的findProperty方法并没有做驼峰转换处理,如下::

  @Override
  public String findProperty(String name, boolean useCamelCaseMapping) {
    return name;
  }

  我们可以改成:

public class CamelMapWrapper extends MapWrapper {
    public CamelMapWrapper(MetaObject metaObject, Map map) {
        super(metaObject, map);
    }

    @Override
    public String findProperty(String name, boolean useCamelCaseMapping) {
        if (useCamelCaseMapping
                && ((name.charAt(0) >= 'A' && name.charAt(0) <= 'Z')
                     || name.indexOf("_") >= 0)) {
            return underlineToCamelhump(name);
        }
        return name;
    }

    /**
     * 将下划线风格替换为驼峰风格
     */
    public String underlineToCamelhump(String inputString) {
        StringBuilder sb = new StringBuilder();

        boolean nextUpperCase = false;
        for (int i = 0; i < inputString.length(); i++) {
            char c = inputString.charAt(i);
            if (c == '_') {
                if (sb.length() > 0) {
                    nextUpperCase = true;
                }
            } else {
                if (nextUpperCase) {
                    sb.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    sb.append(Character.toLowerCase(c));
                }
            }
        }
        return sb.toString();
    }
}

  同时,创建一个自定义的objectWrapperFactory如下:

public class CustomWrapperFactory implements ObjectWrapperFactory {

    @Override
    public boolean hasWrapperFor(Object object) {
        return object != null && object instanceof Map;
    }

    @Override
    public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
        return new CamelMapWrapper(metaObject, (Map) object);
    }

}

  然后,在 MyBatis 配置文件中配置上objectWrapperFactory:


  同样,useCamelCaseMapping最终是通过mapUnderscoreToCamelCase设置注入进来的,所以settings要加上这个设置:


  此时,如果resultType是map类型的话,就可以看到key已经是驼峰式而不是columnName了。
  注意:mybatis提供了一个什么都不做的默认实现DefaultObjectWrapperFactory。

3.7 MetaObject

  MetaObject是一个对象包装器,其性质上有点类似ASF提供的commons类库,其中包装了对象的元数据信息,对象本身,对象反射工厂,对象包装器工厂等。使得根据OGNL表达式设置或者获取对象的属性更为便利,也可以更加方便的判断对象中是否包含指定属性、指定属性是否具有getter、setter等。主要的功能是通过其ObjectWrapper类型的属性完成的,它包装了操作对象元数据以及对象本身的主要接口,操作标准对象的实现是BeanWrapper。BeanWrapper类型有个MetaClass类型的属性,MetaClass中有个Reflector属性,其中包含了可读、可写的属性、方法以及构造器信息。

3.8 对象工厂ObjectFactory

  MyBatis 每次创建结果对象的新实例时,都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂DefaultObjectFactory仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为比如给某些属性设置默认值(有些时候直接修改对象不可行,或者由于不是自己拥有的代码或者改动太大),则可以通过创建自己的对象工厂来实现。ObjectFactory接口定义如下:

public interface ObjectFactory {

  /**
   * Sets configuration properties.
   * @param properties configuration properties
   */
  void setProperties(Properties properties);

  /**
   * Creates a new object with default constructor. 
   * @param type Object type
   * @return
   */
   T create(Class type);

  /**
   * Creates a new object with the specified constructor and params.
   * @param type Object type
   * @param constructorArgTypes Constructor argument types
   * @param constructorArgs Constructor argument values
   * @return
   */
   T create(Class type, List> constructorArgTypes, List constructorArgs);

  /**
   * Returns true if this object can have a set of other objects.
   * It's main purpose is to support non-java.util.Collection objects like Scala collections.
   * 
   * @param type Object type
   * @return whether it is a collection or not
   * @since 3.1.0
   */
   boolean isCollection(Class type);

}

  从这个接口定义可以看出,它包含了两种通过反射机制构造实体类对象的方法,一种是通过无参构造函数,一种是通过带参数的构造函数。同时,为了使工厂类能设置其他属性,还提供了setProperties()方法。
  要自定义对象工厂类,我们可以实现ObjectFactory这个接口,但是这样我们就需要自己去实现一些在DefaultObjectFactory已经实现好了的东西,所以也可以继承这个DefaultObjectFactory类,这样可以使得实现起来更为简单。例如,我们希望给Order对象的属性hostname设置为本地机器名,可以像下面这么实现:

public class CustomObjectFactory extends DefaultObjectFactory{  
  private static String hostname;
  static {
      InetAddress addr = InetAddress.getLocalHost();  
      String ip=addr.getHostAddress().toString(); //获取本机ip  
      hostName=addr.getHostName().toString(); //获取本机计算机名称  
  }
    private static final long serialVersionUID = 1128715667301891724L;  

    @Override  
    public  T create(Class type) {  
        T result = super.create(type);  
        if(type.equals(Order.class)){  
            ((Order)result).setIp(hostname);  
        }  
        return result;  
    }  
}

  接下来,在配置文件中配置对象工厂类为我们创建的对象工厂类CustomObjectFactory。


  此时执行代码,就会发现返回的Order对象中ip字段的值为本机名。

3.9 MappedStatement
  mapper文件或者mapper接口中每个映射语句都对应一个MappedStatement实例,它包含了所有运行时需要的信息比如结果映射、参数映射、是否需要刷新缓存等。MappedStatement定义如下:
public final class MappedStatement {

private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;

public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
}

}
  唯一值得注意的是resultMaps被设计为只读,这样应用可以查看但是不能修改。

3.10 ParameterMapping
  每个参数映射<>标签都被创建为一个ParameterMapping实例,其中包含和结果映射类似的信息,如下:
public class ParameterMapping {

private Configuration configuration;

private String property;
private ParameterMode mode;
private Class javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;

private ParameterMapping() {
}

}

3.11 KeyGenerator

package org.apache.ibatis.executor.keygen;
public interface KeyGenerator {
  // before key generator 主要用于oracle等使用序列机制的ID生成方式
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  // after key generator 主要用于mysql等使用自增机制的ID生成方式
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

3.12 各种Registry
  mybatis将类型处理器,类型别名,mapper定义,语言驱动器等各种信息包装在Registry中维护,如下所示:

public class Configuration {
  ...
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
  ...
}

  各Registry中提供了相关的方法,比如TypeHandlerRegistry中包含了判断某个java类型是否有类型处理器以及获取类型处理器的方法,如下:

  public boolean hasTypeHandler(TypeReference javaTypeReference, JdbcType jdbcType) {
    return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
  }

  public  TypeHandler getTypeHandler(Class type) {
    return getTypeHandler((Type) type, null);
  }

3.13 LanguageDriver

  从3.2版本开始,mybatis提供了LanguageDriver接口,我们可以使用该接口自定义SQL的解析方式。先来看下LanguageDriver接口中的3个方法:

public interface LanguageDriver {

  /**
   * Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.
   * 创建一个ParameterHandler对象,用于将实际参数赋值到JDBC语句中
   * 
   * @param mappedStatement The mapped statement that is being executed
   * @param parameterObject The input parameter object (can be null) 
   * @param boundSql The resulting SQL once the dynamic language has been executed.
   * @return
   * @author Frank D. Martinez [mnesarco]
   * @see DefaultParameterHandler
   */
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  /**
   * Creates an {@link SqlSource} that will hold the statement read from a mapper xml file. 
   * It is called during startup, when the mapped statement is read from a class or an xml file.
   * 将XML中读入的语句解析并返回一个sqlSource对象
   * 
   * @param configuration The MyBatis configuration
   * @param script XNode parsed from a XML file
   * @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
   * @return
   */
  SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType);

  /**
   * Creates an {@link SqlSource} that will hold the statement read from an annotation.
   * It is called during startup, when the mapped statement is read from a class or an xml file.
   * 将注解中读入的语句解析并返回一个sqlSource对象
   * 
   * @param configuration The MyBatis configuration
   * @param script The content of the annotation
   * @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
   * @return 
   */
  SqlSource createSqlSource(Configuration configuration, String script, Class parameterType);

}

  实现了LanguageDriver之后,可以在配置文件中指定该实现类作为SQL的解析器,在XML中我们可以使用 lang 属性来进行指定,如下:


  



  除了可以在语句级别指定外,也可以全局设置,如下:


  

  对于mapper接口,也可以使用@Lang注解,如下所示:

public interface Mapper {
  @Lang(MyLanguageDriver.class) 
  @Select("SELECT * FROM users")
  List selectUser();
}

  LanguageDriver的默认实现类为XMLLanguageDriver。Mybatis默认是XML语言,所以我们来看看XMLLanguageDriver的实现:

public class XMLLanguageDriver implements LanguageDriver {
  // 创建参数处理器,返回默认的实现
  @Override
  public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
  }

  // 根据XML定义创建SqlSource
  @Override
  public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    return builder.parseScriptNode();
  }

  // 解析注解中的SQL语句
  @Override
  public SqlSource createSqlSource(Configuration configuration, String script, Class parameterType) {
    // issue #3
    if (script.startsWith("
                    
                    

你可能感兴趣的:(J2EE)