Mybatis源码阅读系列(三)

12 篇文章 0 订阅
9 篇文章 0 订阅

今天继续来看Configuration,先从代码整个调用链看起吧,一步步往下走,调用链实现在这里会分为两种,根据xml文件的流来生成SqlSessionFactory,另一个是直接自己构建Configuration生成SqlSessionFactory。其中xml读取时早期mybatis的使用方式,自己构建Configuration是现在mybatis-spring的实现方案。

xml读取其实更加自动一点,里面mapper这些配置都是通过配置文件自己去完成的,自己构建Configuration需要多做很多东西,但是也有助于你更好的理解框架原理。

Configuration的addMapper方法

先看下下面这段代码,这是Mybatis3里面的一个测试用例,我由这段代码来看下整个sqlsessionFactory生成过程:

static void setup() throws Exception {
  DataSource dataSource = BaseDataTest.createBlogDataSource();
  BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DDL);
  BaseDataTest.runScript(dataSource, BaseDataTest.BLOG_DATA);
  TransactionFactory transactionFactory = new JdbcTransactionFactory();
  Environment environment = new Environment("Production", transactionFactory, dataSource);
  Configuration configuration = new Configuration(environment);
  configuration.setLazyLoadingEnabled(true);
  configuration.setUseActualParamName(false); // to test legacy style reference (#{0} #{1})
  configuration.getTypeAliasRegistry().registerAlias(Blog.class);
  configuration.getTypeAliasRegistry().registerAlias(Post.class);
  configuration.getTypeAliasRegistry().registerAlias(Author.class);
  configuration.addMapper(BoundBlogMapper.class);
  configuration.addMapper(BoundAuthorMapper.class);
  sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
}

首先拿到数据源和初始化脚本,然后按照我们上一章讲的顺序创建sqlSessionFactory。

这里主要看一下configuration创建后做的几个操作,首先设置懒加载可用,并且配置不使用真实参数。

然后是注册了三个别名。接着调用了configuration.addMapper(BoundBlogMapper.class);方法,后面就直接可以使用sqlsessionFactory去操作mapper了,它完全没有做任何有关mapper.xml相关的扫描和绑定,那么这些绑定是在什么时候生成的?答案就是addMapper方法。

它可以利用接口去反查xml文件并读取里面的配置,但前提是路径要一样,这个在测试的时候比较常用。

我们一步步往下走看看它具体做了什么?

MapperRegistry

addMapper就一句简单的实现:

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

调用了mapperRegistryaddMapper方法,传入了一个类型,该方法实现如下:

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

先做检查,如果不是接口完全不管,因为mapper都必须是接口,然后做一些安全校验,最后往knownMappers里面扔了一个MapperProxyFactory,这里简单说下,就是mapper的xml代理工厂类,利用这个代理工厂可以生成接口和xml文件之间的映射,mapper代理生成过程后面会详细介绍。接下来构建MapperAnnotationBuilder,然后进行解析,其实从名字你应该可以猜出来,MapperAnnotationBuilder其实就是对mybatis自定义注解的解析,主要代码如下:

  public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      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()这个方法就是执行接口到xml的解析,看看它做了些什么:

  private void loadXmlResource() {
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      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());
        xmlParser.parse();
      }
    }
  }

注意看第二行,这里会把类路径的.替换为/,然后去读取资源,如果你的类路径和xml的资源路径一致,那么就能找到对应的文件并进行解析。

后续就是解析注解到configuration里,parseCache()是解析@CacheNamespace注解,parseCacheRef()是解析@CacheNamespaceRef注解。然后解析方法上面的注解,这里分情况,如果是@Select@SelectProvider注解,并且没有@ResultMap注解,将会解析里面配置的@Results

mybatis里面的@ResultMap注解只是一个xml里面的resultMap的映射,真正在注解里面自定义resultMap是使用的@Results,新手很容易搞混。最后解析statement。

总结

今天将整个addMapper逻辑梳理了一下,当调用Configuration.addMapper(xxx.class)时,它实际会将代理的MapperProxyFactory初始化好放到MapperRegistry里面,然后解析接口对应的xml信息或者注解信息,放到Configuration,后面使用的时候通过sqlsession.getMapper方法调用MapperRegistry.getMapper方法,生成一个动态代理返回,最后使用这个代理做所有数据库相关的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值