MyBatis源码深度分析(一):整体流程

工作原理

MyBatis通过解析mybatis-config.xml和Mapper.xml将所有的配置信息都保存在配置类Configuration中(其中XMLConfigBuilder负责解析mybatis-config.xml、XMLMapperBuilder负责解析Mapper.xml),并使用配置类Configuration创建SqlSessionFactory对象,然后通过SqlSessionFactory创建SqlSession对象,最终使用SqlSession创建Mapper代理对象MapperProxy,Mapper接口的所有方法都是由MapperProxy负责执行的。

整体流程

 示例代码
public static void main(String[] args) throws IOException {
    //1.读取mybatis-config.xml 文件
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    //2.创建SqlSessionFactory(建造者设计模式)
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //3.打开SqlSession
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        //4.JDK动态代理获取Mapper接口对象
        StDataMapper mapper = sqlSession.getMapper(StDataMapper.class);
        List<StData> resultList = mapper.findAll();
        for (StData item : resultList) {
            System.out.println("item:" + item.toString());
        }
    }
}

源码分析

1. 解析配置文件mybatis-config.xml
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSessionFactoryBuilder类的build方法负责创建SqlSessionFactory对象

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //使用XMLConfigBuilder解析mybatis-config.xml
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //将所有配置信息都封装在Configuration类中,并使用其创建SqlSessionFactory类
    return build(parser.parse());
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  } finally {
    ErrorContext.instance().reset();
    try {
      if (inputStream != null) {
        inputStream.close();
      }
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

XMLConfigBuilder 对象继承 BaseBuider 抽象类,创建XMLConfigBuilder对象时会实例化配置类Configuration

private XMLConfigBuilder(Class<? extends Configuration> configClass, XPathParser parser, String environment,
    Properties props) {
  //创建配置类Configuration,并封装在XMLConfigBuilder
  super(newConfig(configClass));
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

private static Configuration newConfig(Class<? extends Configuration> configClass) {
  try {
    return configClass.getDeclaredConstructor().newInstance();
  } catch (Exception ex) {
    throw new BuilderException("Failed to create a new Configuration instance.", ex);
  }
}

其parse方法用来解析 mybatis-config.xml 配置文件,并将配置信息保存在配置类Configuration中

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //重点!解析mybatis-config.xml,将配置信息封装在Configuration
  parseConfiguration(parser.evalNode("/configuration"));
  //返回配置类Configuration,用于创建SqlSessionFactory
  return configuration;
}

parseConfiguration方法负责解析mybatis-config.xml配置中各个节点

private void parseConfiguration(XNode root) {
  try {
    // issue #117 read properties first
    //1.解析<properties>节点
    propertiesElement(root.evalNode("properties"));
    //2.解析<settings>节点
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    //3.解析<typeAliases>节点
    typeAliasesElement(root.evalNode("typeAliases"));
    //4.解析<plugins>节点
    pluginElement(root.evalNode("plugins"));
    //5.解析<objectFactory>节点
    objectFactoryElement(root.evalNode("objectFactory"));
    //6.解析<objectWrapperFactory>节点
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    //7.解析<reflectorFactory>节点
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    //8.解析<environments>节点
    environmentsElement(root.evalNode("environments"));
    //9.解析<databaseIdProvider>节点
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    //10.解析<typeHandlers>节点
    typeHandlerElement(root.evalNode("typeHandlers"));
    //11.解析<mappers>节点 重点!!!
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

mapperElement方法负责解析mybatis-config.xml中的mappers节点,MyBatis支持4种Mapper注册方式,这在mapperElement方法中可以得到验证。

<mappers>  
    <!-- 通过package元素将指定包下面的所有Mapper接口进行注册 -->  
    <package name="com.izx.mapper"/>  
    <!-- 通过mapper元素的resource属性指定一个相对于类路径的Mapper.xml文件 -->  
    <mapper resource="com/izx/mapper/StDataMapper.xml"/>  
    <!-- 通过mapper元素的url属性指定一个通过URL请求道的Mapper.xml文件 -->  
     <mapper url="file:com/izx/mapper/StDataMapper.xml"/>
    <!-- 通过mapper元素的class属性指定一个Mapper接口进行注册 -->  
    <mapper class="com.izx.mapper.StDataMapper"/>  
</mappers>

下面将重点分析通过package元素注册指定包下的所有Mapper接口的方式,对于mapperElement方法有两个作用:

  1. 将Mapper接口至Mapper代理工厂MapperProxyFactory的映射添加至Mapper注册器MapperRegistry的 knownMappers集合中(knownMappers是Map<Class<?>, MapperProxyFactory<?>>集合)。
  2. 解析mapper.xml,创建对应的MappedStatement,将其保存在Configration类的mappedStatements集合中
//重点分析通过package元素注册指定包下的所有Mapper接口
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      //1.通过package元素将指定包下面的所有Mapper接口进行注册
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        //重点! 
        //将Mapper接口映射添加至Mapper注册器MapperRegistry
        //解析mapper.xml创建MappedStatement,保存至Configuration.mappedStatements
        configuration.addMappers(mapperPackage);
      } else {
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          //2.通过mapper元素的resource属性指定一个相对于类路径的Mapper.xml文件
          ErrorContext.instance().resource(resource);
          try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
                configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
          //3.通过mapper元素的url属性指定一个通过URL请求获取的Mapper.xml文件
          ErrorContext.instance().resource(url);
          try (InputStream inputStream = Resources.getUrlAsStream(url)) {
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url,
                configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url == null && mapperClass != null) {
          //4.通过mapper元素的class属性指定一个Mapper接口进行注册
          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.");
        }
      }
    }
  }
}

通过package元素注册Mapper接口时使用configuration.addMappers方法将Mapper接口注册工作委托给MapperRegistry

public void addMappers(String packageName) {
  mapperRegistry.addMappers(packageName);
}
1.1. Mapper注册器MapperRegistry

MapperRegistry有两个主要属性:config和KnownMappers

  1. config:  MyBatis全局配置类Configuration
  2. KnownMappers: Map<Class<?>, MapperProxyFactory<?>>类型的集合,实现Mapper类到MapperProxyFactory的映射,将Mapper类添加至集合后必须完成一次Mapper.xml配置解析,如果解析不成功,那么仍然会将mapper接口移除
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

//注册Mapper接口
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 {
      //添加Mapper接口至MapperProxyFactory的映射
      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.
      //重点! 解析mapper.xml生成MappedStatement,保存至Configuration
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      //解析mapper.xml失败时,会将Mapper接口从knownMappers中移除
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}
2. 解析一系列Mapper.xml配置文件
2.1. MapperAnnotationBuilder类

MapperAnnotationBuilder的parse方法将会加载mapper.xml配置文件,并使用XMLMapperBuilder解析mapper.xml

public void parse() {
  String resource = type.toString();
  if (!configuration.isResourceLoaded(resource)) {
    //加载mapper.xml,并使用XMLMapperBuilder解析
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    parseCache();
    parseCacheRef();
    for (Method method : type.getMethods()) {
      if (!canHaveStatement(method)) {
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        parseStatement(method);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

loadXmlResource()方法负责加载并解析mapper.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 1.从src目录下加载mapper.xml
    InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
    if (inputStream == null) {
      //2.src目录下不存在,从resource目录下加载mapper.xml
      // 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) {
      //解析mapper.xml,创建一组MappedStatement,保存在Configuration的mappedStatements
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource,
          configuration.getSqlFragments(), type.getName());
      xmlParser.parse();
    }
  }
}

2.2. MappedStatement类

MappedStatement 类存储了一个 sql 语句的所有信息,Mybatis 通过解析mapper.xml文件 和 mapper 接口上的注解,生成 sql 对应的 MappedStatement 实例, 保存在Configuration 类属性mappedStatements中。

public class Configuration {
  //StrictMap继承自ConcurrentHashMap
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>(
      "Mapped Statements collection")
          .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and "
              + targetValue.getResource());
}

真正执行 mapper 接口中的方法时,会从 Configuration 中找到对应的 MappedStatement,然后进行后续的操作。

3.创建Mapper代理对象MapperProxy
//3.打开SqlSession
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
    //4.JDK动态代理获取Mapper接口对象
    StDataMapper mapper = sqlSession.getMapper(StDataMapper.class);
    List<StData> resultList = mapper.findAll();
    for (StData item : resultList) {
        System.out.println("item:" + item.toString());
    }
}

SqlSession.getMapper(StDataMapper.class)方法内部调用configuration.getMapper()方法,最终调用mapperRegistry.getMapper()方法

public class DefaultSqlSession implements SqlSession {
  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
}

public class Configuration {

  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //获取Mapper对象代理类MapperProxy
    return mapperRegistry.getMapper(type, sqlSession);
  }
}

MapperRegistry的getMapper方法使用MapperProxyFactory类创建Mapper代理对象

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //上面解析mappers节点时,创建Mapper类至MapperProxyFactory的映射
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    //使用JDK动态代理创建MapperProxy代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

MapperProxyFactory使用JDK动态代理生成Mapper代理对象MapperProxy

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    //JDK动态代理创建MapperProxy,Mapper接口的方法都由MapperProxy负责执行
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
 4.Mapper代理对象MapperProxy
MapperProxy实现了InvocationHandler接口,负责执行Mapper接口定义的方法
public class MapperProxy<T> implements InvocationHandler, Serializable {
  //代理过的方法会缓存在methodCache中
  private final Map<Method, MapperMethodInvoker> methodCache;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //Object中定义的方法直接执行,如toString(),hashCode()等
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      }
      //其他Mapper接口定义的方法交由MapperMethodInvoker来执行
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
       //如果method方法已经代理过则从methodCache直接获取,否则创建MethodInvoker并缓存至methodCache
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (!m.isDefault()) {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
        try {
          if (privateLookupInMethod == null) {
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
          throw new RuntimeException(e);
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
}

下一篇将会分析MapperMethod具体执行Sql语句流程,并引入MyBatis的四大对象

  1. 执行器Executor
  2. JDBC Statement处理类StatementHandler
  3. SQL参数处理类ParameterHandler
  4. 查询结果集处理类ResultSetHandler
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值