MyBatis源码简读——2.1.3 mapper接口的加载

mapper接口初始化的方法在MapperAnnotationBuilder 中,它内部的方法还是非常多的

看起来他的方法非常多,单其实和XMLMapperBuilder还是有一些相似之处。

MapperAnnotationBuilder

 

 

其对资源的解析也是在parse方法中

  public void parse() {
    // 判断当前 Mapper 接口是否应加载过。
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      // 加载对应的 XML Mapper
      loadXmlResource();
      // 标记该 Mapper 接口已经加载过
      configuration.addLoadedResource(resource);
      // 设置 namespace 属性
      assistant.setCurrentNamespace(type.getName());
      // 解析 @CacheNamespace 注解
      parseCache();
      // 解析 @CacheNamespaceRef 注解
      parseCacheRef();
      // 遍历每个方法,解析其上的注解
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 执行解析
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          // 解析失败,添加到 configuration 中
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 解析待定的方法
    parsePendingMethods();
  }
  • 首先类似XML的解析,先验证是否加载过,并且在解析前标记接口已经加载、
  • 然后依次解析CacheNamespace,CacheNamespaceRef,以及接口上的注解。
  • 最后开始解析待定的SQL语句

 

parseCache

此方法逻辑是获取注解上的值,根据值进行设置或者创建操作

// 解析 @CacheNamespace 注解
  private void parseCache() {
    // 获得类上的 @CacheNamespace 注解
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    if (cacheDomain != null) {
      // 获得各种属性
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
      // 获得参数属性
      Properties props = convertToProperties(cacheDomain.properties());
      // 创建缓存对象
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
  }

parseCacheRef

类似parseCache操作

// 解析 @CacheNamespaceRef 注解
  private void parseCacheRef() {
    // 获得类上的 @CacheNamespaceRef 注解
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    if (cacheDomainRef != null) {
      // 获得各种属性
      Class<?> refType = cacheDomainRef.value();
      String refName = cacheDomainRef.name();
      // 如果 refType 和 refName 都为空,则抛出 BuilderException 异常
      if (refType == void.class && refName.isEmpty()) {
        throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
      }
      // 校验,如果 refType 和 refName 都不为空,则抛出 BuilderException 异常
      if (refType != void.class && !refName.isEmpty()) {
        throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
      }
      // 获得最终的 namespace 属性
      String namespace = (refType != void.class) ? refType.getName() : refName;
      try {
        // 设置属性
        assistant.useCacheRef(namespace);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
      }
    }
  }

parseStatement

parseStatement是其进行解析的主要方法,主要是针对方法上的注解进行解析

  // 解析方法上的 SQL 操作相关的注解
  void parseStatement(Method method) {
    // 获得参数的类型
    Class<?> parameterTypeClass = getParameterType(method);
    // 获得 LanguageDriver 对象
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 获得 SqlSource 对象
    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 = null;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      // 获得 KeyGenerator 对象
      KeyGenerator keyGenerator;
      String keyProperty = null;
      String keyColumn = null;
      // 假如是更新或者新增
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        // 如果有 @SelectKey 注解,则进行处理
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
          // 如果无 @Options 注解,则根据全局配置处理
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          // 如果有 @Options 注解,则使用该注解的配置处理
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
        // 非新增或者更新
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      // 初始化各种属性
      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();
      }

      // 获得 resultMapId 编号字符串
      String resultMapId = null;
      // 如果有 @ResultMap 注解,使用该注解为 resultMapId 属性
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        resultMapId = String.join(",", resultMapAnnotation.value());
        // 如果无 @ResultMap 注解,解析其它注解,作为 resultMapId 属性
      } else if (isSelect) {
        resultMapId = parseResultMap(method);
      }

      // 构建 MappedStatement 对象
      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);
    }
  }

我们可以把这个逻辑分几步查看

  1. 首先是获取方法参数,语言驱动等各种属性值
  2. 当存在sqlSource的时候进行后续逻辑,继续从方法中获取一部分参数,并且默认一些参数
  3. 然后判断方法上的注解属于哪种操作,假如是更新和新增操作,则根据配置中对GeneratedKeys的配置获得主键的生成策略。
  4. 假如非更新或新增,则不需要主键生成策略
  5. 当方法注解上存在配置信息,则将主键上的配置信息配置到各个属性中
  6. 然后根据注解或者默认方法获得resultMap的主键
  7. 最后使用MapperBuilderAssistant根据之前配置的参数创建MappedStatement

而MapperBuilderAssistant.addMappedStatement的方法和org.apache.ibatis.builder.xml.XMLMapperBuilder中使用的是一套逻辑策略。

总结下来就是从方法中或的各种参数,然后多次验证数据,第一次验证sqlSource,第二次验证方法类型,然后通过统一的工具,将这些参数封装成一个MappedStatement。

MappedStatement

之前XMLMapperBuilder和这次的MapperAnnotationBuilder 在解析的最后一步都是调用MapperBuilderAssistant.addMappedStatement而其最终创建的是一个MappedStatement的对象,这个对象方法非常多,我就不一一列出来了,只列出来一些属性吧。

/**
 * 映射的语句,每个 <select />、<insert />、<update />、<delete />
 * 对应一个 MappedStatement 对象
 * @author Clinton Begin
 */
public final class MappedStatement {

  /**
   * 资源引用的地址
   */
  private String resource;
  /**
   * Configuration 对象
   */
  private Configuration configuration;
  /**
   * 编号
   */
  private String id;
  /**
   * 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)。
   */
  private Integer fetchSize;
  /**
   * 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。
   */
  private Integer timeout;
  /**
   * 语句类型
   */
  private StatementType statementType;
  /**
   * 结果集类型
   */
  private ResultSetType resultSetType;
  /**
   * SqlSource 对象
   */
  private SqlSource sqlSource;
  /**
   * Cache 缓存 对象
   */
  private Cache cache;
  /**
   * ParameterMap 对象
   */
  private ParameterMap parameterMap;
  /**
   * ResultMap 集合
   */
  private List<ResultMap> resultMaps;
  /**
   * 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。
   */
  private boolean flushCacheRequired;
  /**
   * 是否使用缓存
   */
  private boolean useCache;
  /**
   * 这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,
   * 这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
   */
  private boolean resultOrdered;
  /**
   * SQL 语句类型
   */
  private SqlCommandType sqlCommandType;
  /**
   * KeyGenerator 对象
   */
  private KeyGenerator keyGenerator;
  /**
   * (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
   */
  private String[] keyProperties;
  /**
   * (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
   */
  private String[] keyColumns;
  /**
   * 是否有内嵌的 ResultMap
   */
  private boolean hasNestedResultMaps;
  /**
   * 数据库标识
   */
  private String databaseId;
  /**
   * Log 对象
   */
  private Log statementLog;
  /**
   * LanguageDriver 对象
   */
  private LanguageDriver lang;
  /**
   * 这个设置仅对多结果集的情况适用,它将列出语句执行后返回的结果集并每个结果集给一个名称,名称是逗号分隔的。
   */
  private String[] resultSets;

  MappedStatement() {
    // constructor disabled
  }

  public static class Builder {
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.resultSetType = ResultSetType.DEFAULT;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
      mappedStatement.resultMaps = new ArrayList<>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }



}

 

可以看出来,除了全局的配置,它还维护了SQL的相关数据(每个 <select /><insert /><update /><delete /> 对应一个 MappedStatement 对象),每个SQL的参数,结果,主键策略等数据。然后它还提供了根据这些参数获得BoundSql的方法

// 一个非常重要的方法 #getBoundSql(Object parameterObject) 方法,获得 BoundSql 对象
  public BoundSql getBoundSql(Object parameterObject) {
    // 获得 BoundSql 对象
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 忽略,因为 <parameterMap /> 已经废弃,参见 http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html 文档
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    // 判断传入的参数中,是否有内嵌的结果 ResultMap 。如果有,则修改 hasNestedResultMaps 为 true
    // 存储过程相关,暂时无视
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大·风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值