mybatis源码学习7---MappedStatement初始化过程

上一篇我们了解到了MappedStatement类就是mapper.xml中的一个sql语句,而Configuration初始化的时候会加载所有的mapper接口类,而本篇再分析下是如何将mapper接口和xml进行绑定的。

先从上一篇的源码开始分析:

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 {
            knownMappers.put(type, new MapperProxyFactory<T>(type));//加载指定的mapper接口
            //9
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//?
            //10
            parser.parse();
            loadCompleted = true;
          } finally {
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }

如果猜的没错的话,那么第9行和第10行就是解析xml并初始化MappedStatement对象的代码了。那么就先看看MapperAnnotationBuilder(mapper注解构造类)

MapperAnnotationBuilder类的构造方法如下:

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
    String resource = type.getName().replace('.', '/') + ".java (best guess)";
    this.assistant = new MapperBuilderAssistant(configuration, resource);
    this.configuration = configuration;
    this.type = type;

    sqlAnnotationTypes.add(Select.class);
    sqlAnnotationTypes.add(Insert.class);
    sqlAnnotationTypes.add(Update.class);
    sqlAnnotationTypes.add(Delete.class);

    sqlProviderAnnotationTypes.add(SelectProvider.class);
    sqlProviderAnnotationTypes.add(InsertProvider.class);
    sqlProviderAnnotationTypes.add(UpdateProvider.class);
    sqlProviderAnnotationTypes.add(DeleteProvider.class);
  }

MapperAnnotationBuilder类的主要属性有:

private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();//sql语句上的注解
     private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();//指定类指定方法上的sql语句注解
 
     private Configuration configuration;//全局配置对象
     private MapperBuilderAssistant assistant;//Mapper构建助手类,组装解析出来的配置,生成Cache、ResultMap以及MappedStatement对象
     private Class<?> type;//解析的目标mapper接口的Class对象

MapperAnnotationBuilder构造方法就是给自己的属性进行了初始化,其中两个Set集合在初始化时表明了支持 @Select、@Insert、@Update、@Delete以及对应的Provider注解,本篇不再介绍,和xml配置的写法只是写法上不一样而已,实际的作用是一样,本文只分析xml这种用法。

由第一段代码可知,MapperAnnotationBuilder有一个parse方法,也是创建MappedStatement的方法,源码如下:

public void parse() {
    String resource = type.toString();//mapper接口名,如:interface com.lucky.test.mapper.UserMapper
    //Configuration中Set<String> loadedResources 保存着已经加载过的mapper信息
    if (!configuration.isResourceLoaded(resource)) {//判断是否已经加载过
      loadXmlResource();//加载XML资源
      configuration.addLoadedResource(resource);//加载的resource添加到Configuration的loadedResources中
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();//遍历mapper的所有方法
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

可见xml加载是通过调用loadXmlResource方法,源码如下:

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";//通过mapper接口名找到对应的mapper.xml路径
      InputStream inputStream = null;
      try {
        //读取mapper.xml文件流
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        //根据mapper.xml文件流创建XMLMapperBuilder对象
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        //执行parse方法
        xmlParser.parse();
      }
    }
  }

这里又涉及到了XMLMapperBuilder类,很明显是mapper.xml构建者类,构造方法源码如下:

public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments, String namespace) {
    this(inputStream, configuration, resource, sqlFragments);//调用下面一个构造方法
    this.builderAssistant.setCurrentNamespace(namespace);
  }

  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);//调用下面一个构造方法
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    //对自己的属性进行赋值
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);//mapper构造者助手
    this.parser = parser;//XML解析类
    this.sqlFragments = sqlFragments;//sql片段集合
    this.resource = resource;//mapper名称
  }

那么再看下parse方法,源码如下:

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));//从XML解析类中获取mapper标签的内容
      configuration.addLoadedResource(resource);//将加载过的resource添加到Configuration中
      bindMapperForNamespace();//绑定mapper到对应mapper的命名空间中
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

而解析xml的主要方法肯定就是configurationElement方法了,源码如下:

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

很显然这个方法就是将xml中所有可能存在的标签都进行了解析处理,本文就不再详细分析,留得青山在不愁没柴烧。既然知道了xml的各个标签是在哪里解析的,就不愁不知道具体是怎么解析的了。分析到这里,似乎还是没有看到MappedStatement的身影,别急,让我们回到MapperAnnotationBuilder

的parse方法,先是通过loadXmlResource方法进行xml文件的解析加载,然后添加已经加载过的resource到Configuration中,然后解析二级缓存的配置,然后就是获取mapper接口的所有方法,遍历解析:

loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }

parseStatement(method)是获取方法的属性,然后将xml中配置的标签进行一一匹配,源码如下:

void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        } else {
          keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = new NoKeyGenerator();
      }

      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }

最终是调用MapperBuilderAssistant对象的addMappedStatement方法创建了MappedStatement对象,源码如下:

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.addMappedStatement(statement);
    return statement;
  }

创建了MappedStatement对象之后,就调用了Configuration的addMappedStatement方法,从而添加到了Configuration中的Map<String, MappedStatement> mappedStatements集合中,而key就是MappedStatement的id

而到目前为止,MappedStatement对象虽然被创建了,也已经加入到了Configuration管理的集合中去了,可是还没有和mapper接口建立上直接的关系。那么这两个又是如何最终联系到一起去的呢?下一篇再慢慢分析。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值