Mybatis 源码学习(一) Mapper 注册

问题:mybatis中mapper接口和xml文件是如何对应起来的。

在mybatis-config.xml中配置一个mapper

    <mappers>
        <mapper resource="com/tiantao/learn/mappers/UserMapper.xml" />
    </mappers>

是最简单最直接的方式了。这样我们就可以通过SqlSession来获取这个mapper了。

表明上我们获取mapper是通过sqlSession的getMapper函数,稍微看一下源码,发现实际sqlSession调用的Configuration的getMapper函数。继续跟下去,Configuration又调用MapperRegistry类的getMapper函数。

看来MapperRegistry才是最终管理mapper注册和获取的类。

那么mybatis是怎么通过一个xml文件,来注册一个对应的mapper的呢。

研究最简单的加载方式:

public class App 
{
    public static void main( String[] args )
    {
        String resource = "mybatis-config.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

来看一下调用栈:


当我们看到 下面的这个函数实现,心里就豁然开朗了。xml的各种配置方式都在这。

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          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) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            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) {
            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.");
          }
        }
      }
    }
  }   
 
 

再往下看 当resource不为空的时候,最终会去执行mapperParser.parse();这个函数中关键的一个地方,bindMapperForNamespace();

  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

"namespace" 是关键,就是通过这个命名空间来找到对应的接口的,所以要注册一个mapper最终还有要找到这个接口。

原来怀疑是可以通过xml文件的内容直接反射来注册一个mapper的想法是错误的!

最后看一下完整的调用栈:



猜你喜欢

转载自blog.csdn.net/tiantiandjava/article/details/80569451