mybatis源码(二)

362 阅读4分钟

1 扫描并解析mapper文件

1.1 引入mapper文件的四种方式

1.1.1 使用类路径

<mappers>
     <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
     <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
     <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

1.1.2 使用绝对url路径

<mappers>
     <mapper url="file:///var/mappers/AuthorMapper.xml"/>
     <mapper url="file:///var/mappers/BlogMapper.xml"/>
     <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

1.1.3 使用Java类名

<mappers>
     <mapper class="org.mybatis.builder.AuthorMapper"/>
     <mapper class="org.mybatis.builder.BlogMapper"/>
     <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

1.1.4 自动扫描包下所有mapper

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

接下来我们看看mybatis是如何处理这四种情况的。

1.2 解析mapper

public class XMLConfigBuilder extends BaseBuilder {
    private void mapperElement(XNode parent) throws Exception {
      if (parent != null) {
        /**
         * 遍历解析mapper节点
         */
        for (XNode child : parent.getChildren()) {
          /**
           * 首先解析package节点
           *   <mappers>
           *         <package name="org.mybatis.builder"/>
           *   </mappers>
           */
          if ("package".equals(child.getName())) {
            String mapperPackage = child.getStringAttribute("name");
            /**
             * 1.2.1 自动扫描包下所有类
             */
            configuration.addMappers(mapperPackage);
          } else {
            /**
             *  <mappers>
             *       <mapper resource="UserMapper.xml" class="" url=""/>
             *  </mappers>
             */
            String resource = child.getStringAttribute("resource");
            String url = child.getStringAttribute("url");
            String mapperClass = child.getStringAttribute("class");
            /**
             * 三个属性只能有一个是有值的
             */
            if (resource != null && url == null && mapperClass == null) {
              
              //1.2.3 使用resource
              ErrorContext.instance().resource(resource);
              InputStream inputStream = Resources.getResourceAsStream(resource);
              /**
               * 解析mapper.xml
               * 映射器比较复杂,调用XMLMapperBuilder
               * 注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
               */
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              /**
               * 开始解析
               */
              mapperParser.parse();
            } else if (resource == null && url != null && mapperClass == null) {
               //1.2.3 使用url
              ErrorContext.instance().resource(url);
              InputStream inputStream = Resources.getUrlAsStream(url);
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            } else if (resource == null && url == null && mapperClass != null) {
              //1.2.2 指定Java类名
              Class<?> mapperInterface = Resources.classForName(mapperClass);
              configuration.addMapper(mapperInterface);
            } else {
              throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
            }
          }
        }
      }
    }
}

1.2.1 自动扫描包下所有mapper

Configuration#addMappers()

public class Configuration {
 
    public void addMappers(String packageName) {
      mapperRegistry.addMappers(packageName);
    }

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

    public void addMappers(String packageName, Class<?> superType) {
      ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
      //扫描mapper类
      resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
      Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
      //遍历
      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 {
          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);
          }
        }
      }
    }
}

解析接口 MapperAnnotationBuilder#parse

public class MapperAnnotationBuilder {
   
    /**
     * 解析mapper对象
     */
    public void parse() {
      String resource = type.toString();
      /**
       * 如果之前解析了xml,这里就不会再解析
       */
      if (!configuration.isResourceLoaded(resource)) {
        //解析xml
        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));
          }
        }
      }
      /**
       * 解析注解
       */
      parsePendingMethods();
    }
    
    //解析xml
    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";
        // #1347
        InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
        if (inputStream == null) {
          // Search XML mapper that is not in the module but in the classpath.
          try {
            inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
          } catch (IOException e2) {
            // ignore, resource is not required
          }
        }
        if (inputStream != null) {
          XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
          //解析xml 这里先暂不展开 在解析另外三种方式时再聊这块
          xmlParser.parse();
        }
      }
    }
}

1.2.2 使用java类名

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

}

使用java类名的方式 属于 自动扫描包下所有映射器的特殊情况,这里就不重复了。

1.2.3 使用resource和使用绝对url路径的方式

使用resource和使用绝对url路径都是通过XMLMapperBuilder#parse解析的

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

2 解析mapper文件

/**
 * 解析mapper文件里面的节点
 * 拿到配置的配置项,最终封装成一个MapperStatement
 *  //配置mapper元素
 * //  <mapper namespace="org.mybatis.example.BlogMapper">
 * //    <select id="selectBlog" parameterType="int" resultType="Blog">
 * //      select * from Blog where id = #{id}
 * //    </select>
 * //  </mapper>
 *
 */
private void configurationElement(XNode context) {
  try {
    /**
     * 1、配置namespace
     */
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    //2.1 解析cacheRef标签
    cacheRefElement(context.evalNode("cache-ref"));
    //2.2 解析cache标签
    cacheElement(context.evalNode("cache"));
    //2.3 解析parameterMap标签
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //2.4 解析resultMap标签
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //2.5 解析sql标签
    sqlElement(context.evalNodes("/mapper/sql"));
    /**
     * 2.6 解析select update insert delete标签
     */
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

2.1 解析cacheRef标签

cache(二级缓存)只对特定的Namespace使用,即每个namespace使用一个cache实例,如果要多个namespace使用同一个cache实例,则可以使用cache-ref来引用

/**
 * <cache-ref namespace=""/>
 * @param context
 */
private void cacheRefElement(XNode context) {
  if (context != null) {
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      //解析cacheRef
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}

CacheRefResolver#resolveCacheRef

public class CacheRefResolver {
  private final MapperBuilderAssistant assistant;
  private final String cacheRefNamespace;

  public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
  }
}

MapperBuilderAssistant#useCacheRef

public class MapperBuilderAssistant extends BaseBuilder {
  
    public Cache useCacheRef(String namespace) {
      if (namespace == null) {
        throw new BuilderException("cache-ref element requires a namespace attribute.");
      }
      try {
        unresolvedCacheRef = true;
        //根据namespace从configuration找将要引用的cache实例
        Cache cache = configuration.getCache(namespace);
        if (cache == null) {
          throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
        }
        //为cache赋值
        currentCache = cache;
        unresolvedCacheRef = false;
        return cache;
      } catch (IllegalArgumentException e) {
        throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
      }
    }

}

2.2 解析cache标签

每个namesapce的二级缓存默认是关闭的,需要通过cache标签进行开启。

/**
 *     <cache type="" eviction="" readOnly="" size="" blocking="" flushInterval=""></cache>
 * @param context
 */
private void cacheElement(XNode context) {
  if (context != null) {
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    Long flushInterval = context.getLongAttribute("flushInterval");
    Integer size = context.getIntAttribute("size");
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    Properties props = context.getChildrenAsProperties();
    //创建cache
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

MapperBuilderAssistant#useNewCache

public class MapperBuilderAssistant extends BaseBuilder {
    
    public Cache useNewCache(Class<? extends Cache> typeClass,
        Class<? extends Cache> evictionClass,
        Long flushInterval,
        Integer size,
        boolean readWrite,
        boolean blocking,
        Properties props) {
      Cache cache = new CacheBuilder(currentNamespace)
          .implementation(valueOrDefault(typeClass, PerpetualCache.class))
          .addDecorator(valueOrDefault(evictionClass, LruCache.class))
          .clearInterval(flushInterval)
          .size(size)
          .readWrite(readWrite)
          .blocking(blocking)
          .properties(props)
          .build();
      configuration.addCache(cache);
      currentCache = cache;
      return cache;
    }

}

2.3 解析parameterMap标签

/**
 * <parameterMap id="account" type="org.apache.learning.parameter_map.Account">
 *     <parameter property="id" javaType="int"/>
 *     <parameter property="name" javaType="string" jdbcType="VARCHAR"/>
 *     <parameter property="balance" javaType="double" jdbcType="DOUBLE"/>
 * </parameterMap>
 *
 * @param list
 */
private void parameterMapElement(List<XNode> list) {
  for (XNode parameterMapNode : list) {
    String id = parameterMapNode.getStringAttribute("id");
    String type = parameterMapNode.getStringAttribute("type");
    Class<?> parameterClass = resolveClass(type);
    List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
    List<ParameterMapping> parameterMappings = new ArrayList<>();
    for (XNode parameterNode : parameterNodes) {
      String property = parameterNode.getStringAttribute("property");
      String javaType = parameterNode.getStringAttribute("javaType");
      String jdbcType = parameterNode.getStringAttribute("jdbcType");
      String resultMap = parameterNode.getStringAttribute("resultMap");
      String mode = parameterNode.getStringAttribute("mode");
      String typeHandler = parameterNode.getStringAttribute("typeHandler");
      Integer numericScale = parameterNode.getIntAttribute("numericScale");
      ParameterMode modeEnum = resolveParameterMode(mode);
      Class<?> javaTypeClass = resolveClass(javaType);
      JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
      Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
      ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
      parameterMappings.add(parameterMapping);
    }
    builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
  }
}

2.4 解析resultMap标签

public class XMLMapperBuilder extends BaseBuilder {
     /**
     * <!-- 订单查询关联用户的resultMap将整个查询的结果映射到cn.itcast.mybatis.po.Orders中   -->
     *     <resultMap type="cn.itcast.mybatis.po.Orders" id="OrdersUserResultMap">
     *         <!-- 配置映射的订单信息 -->
     *         <!-- id:指定查询列中的唯 一标识,订单信息的中的唯 一标识,如果有多个列组成唯一标识,配置多个id ,column:订单信息的唯 一标识列 ,property:订单信息的唯 一标识 列所映射到Orders中哪个属性  -->
     *
     *         <id column="id" property="id"/>
     *         <result column="user_id" property="userId"/>
     *         <result column="number" property="number"/>
     *         <result column="createtime" property="createtime"/>
     *         <result column="note" property=note/>
     *
     *         <!-- 配置映射的关联的用户信息 -->
     *         <!-- association:用于映射关联查询单个对象的信息property:要将关联查询的用户信息映射到Orders中哪个属性 -->
     *
     *         <association property="user"  javaType="cn.itcast.mybatis.po.User">
     *             <!-- id:关联查询用户的唯 一标识
     *             column:指定唯 一标识用户信息的列
     *             javaType:映射到user的哪个属性-->
     *             <id column="user_id" property="id"/>
     *             <result column="username" property="username"/>
     *             <result column="sex" property="sex"/>
     *             <result column="address" property="address"/>
     *         </association>
     *     </resultMap>
     * @param list
     * @throws Exception
     */
    private void resultMapElements(List<XNode> list) throws Exception {
      for (XNode resultMapNode : list) {
        try {
          resultMapElement(resultMapNode);
        } catch (IncompleteElementException e) {
          // ignore, it will be retried
        }
      }
    }
    
   
    private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
      return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList(), null);
    }

    /**
     * 处理 <resultMap> 节点, 将节点解析成 ResultMap 对象, 下面包含有 ResultMapping 对象组成的列表
     * @param resultMapNode resultMap 节点
     * @param additionalResultMappings 另外的 ResultMapping 列
     * @return ResultMap 对象
     * @throws Exception
     */
    private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
      ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
      // 获取 ID , 默认值会拼装所有父节点的 id 或 value 或 property
      String id = resultMapNode.getStringAttribute("id",
          resultMapNode.getValueBasedIdentifier());
      // 获取 type 属性, 表示结果集将被映射为 type 指定类型的对象
      String type = resultMapNode.getStringAttribute("type",
          resultMapNode.getStringAttribute("ofType",
              resultMapNode.getStringAttribute("resultType",
                  resultMapNode.getStringAttribute("javaType"))));
      // 获取 extends 属性, 其表示结果集的继承
      String extend = resultMapNode.getStringAttribute("extends");
      // 自动映射属性。 将列名自动映射为属性
      Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
      // 解析 type, 获取其类型
      Class<?> typeClass = resolveClass(type);
      if (typeClass == null) {
        typeClass = inheritEnclosingType(resultMapNode, enclosingType);
      }
      Discriminator discriminator = null;
      // 记录解析的结果
      List<ResultMapping> resultMappings = new ArrayList<>();
      resultMappings.addAll(additionalResultMappings);
      // 处理子节点
      List<XNode> resultChildren = resultMapNode.getChildren();
      for (XNode resultChild : resultChildren) {
        // 处理 constructor 节点
        if ("constructor".equals(resultChild.getName())) {
          // 解析构造函数元素,其下的没每一个子节点都会生产一个 ResultMapping 对象
          processConstructorElement(resultChild, typeClass, resultMappings);
        } else if ("discriminator".equals(resultChild.getName())) {
          // 处理 discriminator 节点
          discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
        } else {
          // 处理其余节点, 如 id, result, assosation d等
          List<ResultFlag> flags = new ArrayList<>();
          if ("id".equals(resultChild.getName())) {
            flags.add(ResultFlag.ID);
          }
          // 创建 resultMapping 对象, 并添加到 resultMappings 中
          resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
        }
      }
      // 创建 ResultMapResolver 对象, 该对象可以生成 ResultMap 对象
      ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
      try {
        return resultMapResolver.resolve();
      } catch (IncompleteElementException  e) {
        // 如果无法创建 ResultMap 对象, 则将该结果添加到 incompleteResultMaps 集合中
        configuration.addIncompleteResultMap(resultMapResolver);
        throw e;
      }
    }



}

2.5 解析sql标签

/**
 *  <sql id="sql1">username,age</sql>
 *
 *     <select id="getPerson" parameterType="int" >
 *         select
 *         <include refid="sql1"></include>
 *         from Person where id=#{id}
 *  </select>
 * @param list
 */
private void sqlElement(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    sqlElement(list, configuration.getDatabaseId());
  }
  sqlElement(list, null);
}

2.6 解析select update insert delete标签

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

解析sql比较重要,下文会单独说明