mybatis源码(三)

201 阅读8分钟

上文提到解析mapper文件,解析了cacheRef、cache、resultMap、parameterMap、sql标签,本文主要介绍mybatis是如何解析select、update、insert、delete等标签的,以及对mapper接口代理的准备工作。

1 解析sql

解析sql的过程中,mybatis会对#{}${}进行相应处理,相信看完sql解析的流程,对#{}和${}的区别有一个更清晰的认识。

public class XMLMapperBuilder extends BaseBuilder {

    private void buildStatementFromContext(List<XNode> list) {
      if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
      }
      buildStatementFromContext(list, null);
    }
    

    private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
      for (XNode context : list) {
        final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
        try {
          /**
           * 解析select update insert delete节点
           */
          statementParser.parseStatementNode();
        } catch (IncompleteElementException e) {
          /**
           * sql语句有问题时(比如顺序),存储到集合中,等解析完能解析的再重新解析
           */
          configuration.addIncompleteStatement(statementParser);
        }
      }
    }
}

XMLStatementBuilder#parseStatementNode mybatis使用MappedStatement封装一条<select|update|delete|insert>节点的信息, 有了它Mybatis就知道如何去调度四大组件顺利的完成用户请求

public class XMLStatementBuilder extends BaseBuilder {

    /**
     * 解析 #{} ${}
     */
    public void parseStatementNode() {
      String id = context.getStringAttribute("id");
      String databaseId = context.getStringAttribute("databaseId");

      if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        return;
      }

      Integer fetchSize = context.getIntAttribute("fetchSize");
      Integer timeout = context.getIntAttribute("timeout");
      String parameterMap = context.getStringAttribute("parameterMap");
      String parameterType = context.getStringAttribute("parameterType");
      Class<?> parameterTypeClass = resolveClass(parameterType);
      String resultMap = context.getStringAttribute("resultMap");
      String resultType = context.getStringAttribute("resultType");
      /**
       * 解析配置的自定义脚本语言驱动
       */
      String lang = context.getStringAttribute("lang");
      LanguageDriver langDriver = getLanguageDriver(lang);

      Class<?> resultTypeClass = resolveClass(resultType);
      String resultSetType = context.getStringAttribute("resultSetType");
      StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
      ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

      String nodeName = context.getNode().getNodeName();
      SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      /**
       * 是否刷新缓存 默认值 查询不刷新 增删改刷新
       */
      boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
      /**
       * 是否使用二级缓存  默认值 查询使用 增删改不使用
       */
      boolean useCache = context.getBooleanAttribute("useCache", isSelect);
      /**
       * 是否需要处理嵌套查询结果 group by
       */
      boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

      // Include Fragments before parsing
      XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
      /**
       * 替换include标签为对应的sql标签里面的值
       */
      includeParser.applyIncludes(context.getNode());

      // Parse selectKey after includes and remove them.
      /**
       * 1.1 解析selectKey
       */
      processSelectKeyNodes(id, parameterTypeClass, langDriver);

      // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
      /**
       * 解析sql #{}在此时被替换为 ?  ${}未作处理
       * 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且只有#{}的时候,直接静态解析 使用?占位
       * ${}暂不解析
       */

      /**
       * 实现类 XMLLanguageDriver
       */
       //1.2 解析sql
      //XMLLanguageDriver.createSqlSource()
      SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
      String resultSets = context.getStringAttribute("resultSets");
      String keyProperty = context.getStringAttribute("keyProperty");
      String keyColumn = context.getStringAttribute("keyColumn");
      /**
       * 设置主键自增规则
       */
      KeyGenerator keyGenerator;
      String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
      keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
      if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
      } else {
        keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
            configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
            ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      }
      
      // 1.3 
      /**
       *  MappedStatement : 保存sql的所有信息
       */
      builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
          fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
          resultSetTypeEnum, flushCache, useCache, resultOrdered,
          keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }

}

1.1 解析selectKey标签

假如有些数据库不支持自增主键,或者说我们想插入自定义的主键,而又不想在业务代码中编写逻辑,那么就可以通过MyBatis的selectKey来获取。

<insert id="add" parameterType="com.demo.pojo.User">
        <!--通过mybatis框架提供的selectKey标签获得自增产生的ID值-->
        <selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(code,name,remark,sex)
                      values
        (#{code},#{name},#{remark},#{sex})
</insert>

selectKey 会将 SELECT LAST_INSERT_ID()的结果放入到传入的model的主键里面,即获取数据库里自动生成的id

  • keyProperty:对应的model中的主键的属性名,跟数据库的主键对应

  • order:有2个选择:BEFORE和AFTER。

    AFTER 表示 SELECT LAST_INSERT_ID() 在insert执行之后执行,多用于自增主键 BEFORE 表示 SELECT LAST_INSERT_ID() 在insert执行之前执行,这样的话就拿不到主键了,适合那种主键不是自增的类型,这样我们就可以插入自定义的主键

  • resultType:主键类型

selectKey中返回的值只能有一条数据,如果满足条件的数据有多条会报错,所以一般都是用于生成主键,确保唯一,或者在selectKey后面的语句加上条件,确保唯一

public class XMLStatementBuilder extends BaseBuilder {
 
    private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
      List<XNode> selectKeyNodes = context.evalNodes("selectKey");
      if (configuration.getDatabaseId() != null) {
        parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId());
      }
      parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null);
      removeSelectKeyNodes(selectKeyNodes);
    }

    private void parseSelectKeyNodes(String parentId, List<XNode> list, Class<?> parameterTypeClass, LanguageDriver langDriver, String skRequiredDatabaseId) {
      for (XNode nodeToHandle : list) {
        String id = parentId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
        String databaseId = nodeToHandle.getStringAttribute("databaseId");
        if (databaseIdMatchesCurrent(id, databaseId, skRequiredDatabaseId)) {
          //处理
          parseSelectKeyNode(id, nodeToHandle, parameterTypeClass, langDriver, databaseId);
        }
      }
    }

    private void parseSelectKeyNode(String id, XNode nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver, String databaseId) {
      String resultType = nodeToHandle.getStringAttribute("resultType");
      Class<?> resultTypeClass = resolveClass(resultType);
      StatementType statementType = StatementType.valueOf(nodeToHandle.getStringAttribute("statementType", StatementType.PREPARED.toString()));
      String keyProperty = nodeToHandle.getStringAttribute("keyProperty");
      String keyColumn = nodeToHandle.getStringAttribute("keyColumn");
      
      //
      boolean executeBefore = "BEFORE".equals(nodeToHandle.getStringAttribute("order", "AFTER"));

      //defaults
      boolean useCache = false;
      boolean resultOrdered = false;
      KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
      Integer fetchSize = null;
      Integer timeout = null;
      boolean flushCache = false;
      String parameterMap = null;
      String resultMap = null;
      ResultSetType resultSetTypeEnum = null;
      //解析sql 
      SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
      SqlCommandType sqlCommandType = SqlCommandType.SELECT;
      //将sql信息封装成MappedStatement对象 添加到Configuration
      builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
          fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
          resultSetTypeEnum, flushCache, useCache, resultOrdered,
          keyGenerator, keyProperty, keyColumn, databaseId, langDriver, null);

      id = builderAssistant.applyCurrentNamespace(id, false);

      MappedStatement keyStatement = configuration.getMappedStatement(id, false);
      configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
    }

}

1.2 解析sql

XMLLanguageDriver#createSqlSource()

处理sql中包含#{}和${}的情况

public class XMLLanguageDriver implements LanguageDriver {
    
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
      XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
      return builder.parseScriptNode();
    }

}

XMLScriptBuilder#parseScriptNode

sql解析步骤:
1、根据sql中有无${}判断是否是动态的
有${} 就是动态的
无${}(不管有没有#{}) 就是非动态的

2、${}在运行时在替换成具体的值(即拼接),此时不做任何处理
   #{}在此时替换成? 运行期间由PreparedStatementHandler设置具体的值
   
public class XMLScriptBuilder extends BaseBuilder {
    public SqlSource parseScriptNode() {
      /**
       * 根据sql中是否有${}来判断
       * 有${} isDynamic返回true
       * 无${}(不管有没有#{}) isDynamic返回false
       */

      MixedSqlNode rootSqlNode = parseDynamicTags(context);
      SqlSource sqlSource = null;
      if (isDynamic) {
      
         //1.2.1 解析${}
        /**
         * 当 SQL 配置中包含 ${}(不是 #{})占位符,或者包含 <if>、<where> 等标签时,会被认为是动态 SQL,此时使用 DynamicSqlSource 存储 SQL 片段(可能会包含很多SqlNode)
         *  具体来说 ${}占位符由TextSqlNode存储
         * ${} 由DynamicCheckerTokenParser来解析 其解析动作就是不解析,在运行时替换成具体的值   在parseDynamicTags()方法就完成了
         */
        sqlSource = new DynamicSqlSource(configuration, rootSqlNode);

      } else {
        //1.2.2 解析#{}
        /**
         * 使用占位符方式来解析 #{}    由ParameterMappingTokenHandler.handlerToken来解析 在RawSqlSource的构造方法中解析
         * #{} 现在就替换成 ?  不必等到运行期间再去替换 运行期间使用PreparedStatement来操作占位符
         */
        sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
      }
      return sqlSource;
    }
}

解析dynamicTags的流程

如果是CDATA域或纯文本,调用TextSqlNode#isDynamic进行判断

public class XMLScriptBuilder extends BaseBuilder {
    
    private boolean isDynamic;

    /**
     * StaticTextSqlNode 用于存储静态文本,TextSqlNode 用于存储带有 ${} 占位符的文本,
     * IfSqlNode 则用于存储 <if> 节点的内容。MixedSqlNode 内部维护了一个 SqlNode 集合,用于存储各种各样的 SqlNode
     * @param node
     * @return
     */
    protected MixedSqlNode parseDynamicTags(XNode node) {
      List<SqlNode> contents = new ArrayList<>();
      NodeList children = node.getNode().getChildNodes();
      for (int i = 0; i < children.getLength(); i++) {
        XNode child = node.newXNode(children.item(i));
        /**
         * 判断是否是cdata或纯文本
         */
        if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
          String data = child.getStringBody("");
          TextSqlNode textSqlNode = new TextSqlNode(data);
          /**
           * 1.2.1.1  isDynamic():会去判断${}并做解析(实际上啥都不会做)
           */
          if (textSqlNode.isDynamic()) {
            contents.add(textSqlNode);
            isDynamic = true;
          } else {
            contents.add(new StaticTextSqlNode(data));
          }
        } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
          String nodeName = child.getNode().getNodeName();
          NodeHandler handler = nodeHandlerMap.get(nodeName);
          if (handler == null) {
            throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
          }
          handler.handleNode(child, contents);
          isDynamic = true;
        }
      }
      return new MixedSqlNode(contents);
    }
}

在看TextSqlNode#isDynamic方法之前,先来了解一下GenericTokenParser占位符解析器, GenericTokenParser 是一个通用解析器,并非只能解析 GenericTokenParser是一个通用的标记解析器,用于解析形如{},GenericTokenParser 是一个通用的标记解析器,用于解析形如 {xxx},#{xxx} 等标记。 GenericTokenParser 负责将标记中的内容抽取出来,并将标记内容交给相应的 TokenHandler 去处理。

GenericTokenParser负责查找占位符,找到占位符后交给TokenHandler处理

${} -> DynamicCheckerTokenParser#handleToken()

#{} -> ParameterMappingTokenHandler#handleToken()

public class GenericTokenParser {

    private final String openToken;
    private final String closeToken;
 

    public String parse(String text) {
      if (text == null || text.isEmpty()) {
        return "";
      }
      // search open token
      int start = text.indexOf(openToken);
      if (start == -1) {
        return text;
      }
      char[] src = text.toCharArray();
      int offset = 0;
      final StringBuilder builder = new StringBuilder();
      StringBuilder expression = null;
      while (start > -1) {
        if (start > 0 && src[start - 1] == '\') {
          // this open token is escaped. remove the backslash and continue.
          builder.append(src, offset, start - offset - 1).append(openToken);
          offset = start + openToken.length();
        } else {
          // found open token. let's search close token.
          if (expression == null) {
            expression = new StringBuilder();
          } else {
            expression.setLength(0);
          }
          builder.append(src, offset, start - offset);
          offset = start + openToken.length();
          int end = text.indexOf(closeToken, offset);
          while (end > -1) {
            if (end > offset && src[end - 1] == '\') {
              // this close token is escaped. remove the backslash and continue.
              expression.append(src, offset, end - offset - 1).append(closeToken);
              offset = end + closeToken.length();
              end = text.indexOf(closeToken, offset);
            } else {
              expression.append(src, offset, end - offset);
              offset = end + closeToken.length();
              break;
            }
          }
          if (end == -1) {
            // close token was not found.
            builder.append(src, start, src.length - start);
            offset = src.length;
          } else {
         
            /**
             * 找到#{}或${},调用handler.handleToken()完成解析
             *
             * 不同的handler处理的不一样 有处理${}的 有处理#{}的
             *
             * ${} -> DynamicCheckerTokenParser#handleToken()
             * #{} -> ParameterMappingTokenHandler#handleToken()
             */
            builder.append(handler.handleToken(expression.toString()));
            offset = end + closeToken.length();
          }
        }
        start = text.indexOf(openToken, offset);
      }
      if (offset < src.length) {
        builder.append(src, offset, src.length - offset);
      }
      return builder.toString();
    }
}

1.2.1 ${}解析

1.2.1.1 mybatis启动时${}解析

${}是在TextSqlNode#isDynamic方法中解析的。

TextSqlNode#isDynamic

创建GenericTokenParser时指定解析{},调用GenericTokenParser#parse查找文本{},找到后交给DynamicCheckerTokenParser处理

public class TextSqlNode implements SqlNode {
   
    /**
     * 判断是否是动态sql
     * @return
     */
    public boolean isDynamic() {
      //${}对应的TokenHandler
      DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
      //指定判断是否动态sql的依据 即是否包含${} 重要
      GenericTokenParser parser = createParser(checker);
      parser.parse(text);
      return checker.isDynamic();
    }
    

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

DynamicCheckerTokenParser#handleToken的处理逻辑

  private static class DynamicCheckerTokenParser implements TokenHandler {

    private boolean isDynamic;

    public DynamicCheckerTokenParser() {
      // Prevent Synthetic Access
    }

    public boolean isDynamic() {
      return isDynamic;
    }

    /**
     * 当扫描到${}的时候调用此方法,其实就是不解析 在运行时替换成具体的值
     * @param content
     * @return
     */
    @Override
    public String handleToken(String content) {
      this.isDynamic = true;
      return null;
    }
  }

}

1.2.1.2 mapper接口方法调用时${}解析

SqlSource.getBoundSql方法在调用mapper接口时会对sql进行解析

${}对应的SqlSource为DynamicSqlSource,DynamicSqlSource#getBoundSql在CachingExecutor#query()方法中被调用,即在执行mapper接口中的方法时,解析${}做拼接操作

public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    //创建DynamicContext
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    /**
     * 解析sql
     */
    //rootSqlNode ==> MixedSqlNode
    /**
     *  ${} 在MixedSqlNode.apply()方法中被映射
     */
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();

    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    // 调用 StaticSqlSource 的 getBoundSql 获取 BoundSql
    //StaticSqlSource
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql 中
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

}

1.2.2 #{}解析

1.2.2.1 mybatis启动时#{}解析

#{}对应的SqlSource为RawSqlSource,RawSqlSource对象是在mybatis启动过程中创建的

public class RawSqlSource implements SqlSource {


  //StaticSqlSource
  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    /**
     * 解析#{}
     */
    //sqlSource类型为StaticSqlSource
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }
}

在RawSqlSource的构造器中,调用SqlSourceBuilder.parse对sql进行解析

SqlSourceBuilder.parse

public class SqlSourceBuilder extends BaseBuilder {

  private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";

  public SqlSourceBuilder(Configuration configuration) {
    super(configuration);
  }

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    /**
       #{}对应的TokenHandler
     * 创建 #{} 占位符处理器
     */
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    /**
     * 创建 #{} 占位符解析器
     */
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
    /**
     * 去找#{} 找到后调用ParameterMappingTokenHandler.handleToken()方法去解析
     * 把#{}替换为?
     */
    String sql = parser.parse(originalSql);
   
    /**
     *  originalSql: select * from person where id = #{id}
     *  sql: select * from person where id = ?
     */
//    封装解析结果到 StaticSqlSource 中,并返回
    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
  }
  
}

ParameterMappingTokenHandler#handleToken处理#{}的流程

在mybatis启动时将 #{} 替换为 ?

private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {

  private List<ParameterMapping> parameterMappings = new ArrayList<>();
  private Class<?> parameterType;
  private MetaObject metaParameters;

  public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
    super(configuration);
    this.parameterType = parameterType;
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }

  public List<ParameterMapping> getParameterMappings() {
    return parameterMappings;
  }

  @Override
  public String handleToken(String content) {
    //构建 content 的对应的 ParameterMapping
    parameterMappings.add(buildParameterMapping(content));
    return "?";
  }

  private ParameterMapping buildParameterMapping(String content) {
    /**
     * 将 #{xxx} 占位符中的内容解析成 Map。大家可能很好奇一个普通的字符串是怎么解析成 Map 的,
     * 举例说明一下。如下:
     *
     *    #{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
     *
     * 上面占位符中的内容最终会被解析成如下的结果:
     *
     *  {
     *      "property": "age",
     *      "typeHandler": "MyTypeHandler",
     *      "jdbcType": "NUMERIC",
     *      "javaType": "int"
     *  }
     *
     * parseParameterMapping 内部依赖 ParameterExpression 对字符串进行解析,ParameterExpression 的
     * 逻辑不是很复杂,这里就不分析了。大家若有兴趣,可自行分析
     */
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
      propertyType = metaParameters.getGetterType(property);
      /**
       * parameterType 是运行时参数的类型。如果用户传入的是单个参数,比如 Article 对象,此时
       * parameterType 为 Article.class。如果用户传入的多个参数,比如 [id = 1, author = "coolblog"],
       * MyBatis 会使用 ParamMap 封装这些参数,此时 parameterType 为 ParamMap.class。如果
       * parameterType 有相应的 TypeHandler,这里则把 parameterType 设为 propertyType
       */
    } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
      propertyType = parameterType;
    } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
      propertyType = java.sql.ResultSet.class;
    } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
      propertyType = Object.class;
    } else {
      /**
       * 代码逻辑走到此分支中,表明 parameterType 是一个自定义的类,
       * 比如 Article,此时为该类创建一个元信息对象
       */
      MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
      // 检测参数对象有没有与 property 想对应的 getter 方法
      if (metaClass.hasGetter(property)) {
        // 获取成员变量的类型
        propertyType = metaClass.getGetterType(property);
      } else {
        propertyType = Object.class;
      }
    }
    //省略部分代码
  }
}

1.2.2.2 调用mapper接口方法时#{} 解析

SqlSource.getBoundSql方法在调用mapper接口时会对sql进行解析

RawSqlSource#getBoundSql

public class RawSqlSource implements SqlSource {


    //StaticSqlSource
    private final SqlSource sqlSource;
  
    public BoundSql getBoundSql(Object parameterObject) {
      //StaticSqlSource#getBoundSql
      return sqlSource.getBoundSql(parameterObject);
    }
}

StaticSqlSource.getBoundSql

public class StaticSqlSource implements SqlSource {
    //创建StaticSqlSource时传入的sql
    private final String sql;
    
    public BoundSql getBoundSql(Object parameterObject) {
      return new BoundSql(configuration, sql, parameterMappings, parameterObject);
    }
}

1.3 将sql信息封装到MappedStatement

MapperBuilderAssistant#addMappedStatement()

public class MapperBuilderAssistant extends BaseBuilder {
  
    public MappedStatement addMappedStatement(
        String id,
        SqlSource sqlSource,
        StatementType statementType,
        SqlCommandType sqlCommandType,
        Integer fetchSize,
        Integer timeout,
        String parameterMap,
        Class<?> parameterType,
        String resultMap,
        Class<?> resultType,
        ResultSetType resultSetType,
        boolean flushCache,
        boolean useCache,
        boolean resultOrdered,
        KeyGenerator keyGenerator,
        String keyProperty,
        String keyColumn,
        String databaseId,
        LanguageDriver lang,
        String resultSets) {

      if (unresolvedCacheRef) {
        throw new IncompleteElementException("Cache-ref not yet resolved");
      }

      id = applyCurrentNamespace(id, false);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      
      //建造者模式
      MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
          .resource(resource)
          .fetchSize(fetchSize)
          .timeout(timeout)
          .statementType(statementType)
          .keyGenerator(keyGenerator)
          .keyProperty(keyProperty)
          .keyColumn(keyColumn)
          .databaseId(databaseId)
          .lang(lang)
          .resultOrdered(resultOrdered)
          .resultSets(resultSets)
          .resultMaps(getStatementResultMaps(resultMap, resultType, id))
          .resultSetType(resultSetType)
          .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
          .useCache(valueOrDefault(useCache, isSelect))
          .cache(currentCache);

      ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
      if (statementParameterMap != null) {
        statementBuilder.parameterMap(statementParameterMap);
      }

      MappedStatement statement = statementBuilder.build();
      
      //添加到全局的Configuration中
      configuration.addMappedStatement(statement);
      return statement;
    }

}

Configuration#addMappedStatement

在执行mapper中的方法时,可以根据id从mappedStatements取出sql对应的MappedStatement对象

public class Configuration {
    //添加到集合中
    public void addMappedStatement(MappedStatement ms) {
      mappedStatements.put(ms.getId(), ms);
    }
}

MappedStatement封装了sql的哪些信息?

public final class MappedStatement {

  /**
   * 对应所属mapper的资源路径,如我们示例中的CompanyMapper.xml
   */
  private String resource;
  /**
   * mybatis全局的配置对象
   */
  private Configuration configuration;
  /**
   * 当前MappedStatement的唯一识别ID,并且在同一个Configuration中是唯一的
   * 它由Mapper类的完全限定名和Mapper方法名称拼接而成
   */
  private String id;
  /**
   * mybatis每次从数据库中返回记录的大小,通过对该值的优化,可以提升查询效率
   */
  private Integer fetchSize;
  /**
   * 当前MappedStatement执行时,数据库操作的超时时间
   */
  private Integer timeout;
  /**
   * SQL声明的类型,决定当前MappedStatement由哪种类型的StatementHandler执行
   * StatementType枚举有:STATEMENT, PREPARED, CALLABLE
   * 默认值是:PREPARED
   */
  private StatementType statementType;
  /**
   * 结果集处理类型,决定了结果集游标的移动方式:
   * 只能向前移动、双向移动且对修改敏感、双向移动对修改不敏感。
   */
  private ResultSetType resultSetType;
  /**
   * 存储我们定义的经过mybatis初步解析处理的sql语句,由若干sql节点构成,包含一些动态节点,如If条件语句。
   * 在生成SqlSource之前,已经把<include></include>标签的内容转为了实际的文本对象
   */
  private SqlSource sqlSource;
  /**
   * 二级缓存策略配置对象
   */
  private Cache cache;
  /**
   * 参数映射,外部以何种形式对当前MappedStatement传参
   */
  private ParameterMap parameterMap;

  /**
   * 结果映射列表,应该是只有一个的,不明白为啥是列表,可能是多个结果集返回时使用的。
   */
  private List<ResultMap> resultMaps;
  /**
   * 是否要刷新缓存,将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空
   * 对select命令,默认值为false,对insert、update、delete默认为true。
   */
  private boolean flushCacheRequired;
  /**
   * 将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
   */
  private boolean useCache;

  private boolean resultOrdered;
  /**
   * sql命令类型:如select、update、insert、delete等
   */
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  /**
   * 语言驱动,如xml。
   */
  private LanguageDriver lang;
  /**
   * 结果集类型列表
   */
  private String[] resultSets;
}

至此,mapper对象解析完毕。

public class XMLMapperBuilder extends BaseBuilder {
  
    public void parse() {
      //判断是否解析过
      if (!configuration.isResourceLoaded(resource)) {
        //解析mapper.xml文件
        configurationElement(parser.evalNode("/mapper"));
        //代表已经解析过了
        configuration.addLoadedResource(resource);
        /**
         * 绑定namespace里面的class对象
         */
        bindMapperForNamespace();
      }

      /**
       * 重新解析之前解析不了的节点
       */
      parsePendingResultMaps();
      parsePendingCacheRefs();
      parsePendingStatements();
    }
}

此时,configurationElement(parser.evalNode("/mapper"))这行代码执行完了。下面就会绑定namespace里面的class对象

2 mapper接口的代理逻辑

public class XMLMapperBuilder extends BaseBuilder {
 
    private void bindMapperForNamespace() {
      String namespace = builderAssistant.getCurrentNamespace();
      if (namespace != null) {
        Class<?> boundType = null;
        try {
          //根据mapper的全限路径生成对应的Class对象
          boundType = Resources.classForName(namespace);
        } catch (ClassNotFoundException e) {
          //ignore, bound type is not required
        }
        if (boundType != null) {
          if (!configuration.hasMapper(boundType)) {
            // Spring may not know the real resource name so we set a flag
            // to prevent loading again this resource from the mapper interface
            // look at MapperAnnotationBuilder#loadXmlResource
            configuration.addLoadedResource("namespace:" + namespace);
            //添加到Configuration的mapper集合
            configuration.addMapper(boundType);
          }
        }
      }
    }
}

Configuration#addMapper

public class Configuration {

    protected final MapperRegistry mapperRegistry = new MapperRegistry(this);


    public <T> void addMapper(Class<T> type) {
      mapperRegistry.addMapper(type);
    }

}
public class MapperRegistry {

    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    public <T> void addMapper(Class<T> type) {
      if (type.isInterface()) {
        //是否已经解析过
        if (hasMapper(type)) {
          throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
        }
        boolean loadCompleted = false;
        try {
        
         
         /**
         *  k - mapper接口的Class对象
         *  v - MapperProxyFactory mapper代理工厂
         */

          knownMappers.put(type, new MapperProxyFactory<>(type));
          // It's important that the type is added before the parser is run
          // otherwise the binding may automatically be attempted by the
          // mapper parser. If the type is already known, it won't try.
          MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
          //解析接口的注解 前面已做过说明 这里不在重复
          parser.parse();
          loadCompleted = true;
        } finally {
          if (!loadCompleted) {
            knownMappers.remove(type);
          }
        }
      }
    }
}

至此,mybatis的配置文件解析完毕,下面,我们将会分析mapper接口的代理对象的生成及其代理逻辑。