Configuration--mappers--addMappers(三-8-1)

本篇文章,我们来讲讲,当mapper标签加载的是一个类时,ConfigurationaddMappers方法的解析过程.

mapper标签引用类的情况:

<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <package name="org.mybatis.builder"/>
</mappers>

可以看出,一种是单独引用一个类,一种是一次性引入一个包下所有类.而addMappers方法有两种形式的重载:

  // Configuration中的成员变量MapperRegistry
  protected MapperRegistry mapperRegistry = new MapperRegistry(this);

  // 一次加载一个包下的类
  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }

  // 一次加载一个类
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

从上述代码可以看出,Configuration中实际是使用了MapperRegistryaddMappers方法,我们先来看MapperRegistry中一次加载一个包下所有类的方法:

  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }

  public void addMappers(String packageName, Class<?> superType) {
    // 找到该包下所有的Java类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    // 循环调用addMapper(Class<T> type)
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

从上述代码中,我们可以看出,加载一个包下所有的类,最后还是循环调用一次加载一个类的方法:

  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 {
        // MapperProxyFactory是Mapper的代理,之后用到的时候我们在讲
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // 构造一个MapperAnnotationBuilder去解析这个类
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        // 若上面加载时出错,则将该类移除
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

  public <T> boolean hasMapper(Class<T> type) {
    // knownMappers为MapperRegistry的一个Map结构,用来保存已经加载过的类信息和代理工厂的对应关系
    return knownMappers.containsKey(type);
  }

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

从上述代码可以看出,对具体类的解析是在MapperAnnotationBuilder中完成的,我们先来看看MapperAnnotationBuilder的构造方法:

  private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>();
  private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<>();

  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);
  }

接下来看比较重要的parse()方法:

  public void parse() {
    String resource = type.toString();
    // 是否已经加载过该resource(值形式类似于为:interface org.tree.study.mybatis.dao.UserDao)
    if (!configuration.isResourceLoaded(resource)) {
      // 加载类对应的xml文件
      loadXmlResource();
      // 将该resource加入已经加载过的Set中
      configuration.addLoadedResource(resource);
      // 设置命名空间
      assistant.setCurrentNamespace(type.getName());
      // 加载CacheNamespace注解
      parseCache();
      // 加载CacheNamespaceRef注解
      parseCacheRef();
      // 加载类中所有的方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // 解析方法
          parseStatement(method);
        } catch (IncompleteElementException e) {
          // 将异常解析的方法加入一个List中
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 重新处理异常解析的方法
    parsePendingMethods();
  }

上述代码逻辑比较复杂,但是总的来说,核心是去加载xml文件和加载该类上的注解(这也证明mybatis支持xml配置和注解配置两种方式).

我们先来看loadXmlResource()方法:

  private void loadXmlResource() {
    // 看是否已经加载过该xml文件
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      // 从这里我们可以看出,若是要使用加载类的方法,xml文件的路径需要和类的包名路径一致,并且xml文件名要和类名一致(如:org.tree.study.mybatis.dao.UserDao对应org/tree/study/mybatis/dao/UserDao.xml)
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        // 从这里我们看出,加载xml文件时,使用了XMLMapperBuilder,这正是我们要讲的另一种方式,这里的详细过程,我们下一节再讲
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

parseCache()方法和parseCacheRef()方法涉及到缓存,我们之后再讲,我们主要来看parseStatement方法:

  void parseStatement(Method method) {
    // 若该方法只有一个参数,则返回该参数本身Class对象,如果大于一个参数,则固定返回ParamMap.class
    Class<?> parameterTypeClass = getParameterType(method);
    // 该方法上是否定义了@Lang注解,否则获取一个默认的LanguageDriver 
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 通过方法上的注解生成一个SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      // 这个mappedStatementId比较重要,就是每个语句的唯一标识
      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) {
        flushCache = options.flushCache();
        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);
      }

      // 最终调用addMappedStatement方法
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          null,                             // ParameterMapID
          parameterTypeClass,
          resultMapId,    // ResultMapID
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          false, // TODO issue #577
          keyGenerator,
          keyProperty,
          keyColumn,
          null,
          languageDriver,
          null);
    }
  }

上面的方法看起来比较复杂,主要就是解析各种注解,最终调用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);
    statementBuilder.resource(resource);
    statementBuilder.fetchSize(fetchSize);
    statementBuilder.statementType(statementType);
    statementBuilder.keyGenerator(keyGenerator);
    statementBuilder.keyProperty(keyProperty);
    statementBuilder.keyColumn(keyColumn);
    statementBuilder.databaseId(databaseId);
    statementBuilder.lang(lang);
    statementBuilder.resultOrdered(resultOrdered);
    statementBuilder.resulSets(resultSets);
    setStatementTimeout(timeout, statementBuilder);

    setStatementParameterMap(parameterMap, parameterType, statementBuilder);
    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
    setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

    MappedStatement statement = statementBuilder.build();
    // 最终调用该方法,将MappedStatement加入到Configuration中
    configuration.addMappedStatement(statement);
    return statement;
  }

  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

综上,我们可以看出,当我们使用class类去配置mybatis时,可以使用注解的方法,也可以使用xml文件,但是该xml文件需要和class类的路径相同.最终每条sql或者说每个接口方法都会被解析成一个mappedStatement加入到Configuration中.每个mappedStatement有其唯一的id(在该篇文章中,我们看到其中使用的id是class的类名+方法名).

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值