mybatis源码学习:从SqlSessionFactory到代理对象的生成

栏目: IT技术 · 发布时间: 4年前

内容简介:一、首先读取类路径下的配置文件,获取其字节输入流。二、创建SqlSessionFactoryBuilder对象,调用内部的build方法。三、根据字节输入流创建XMLConfigBuilder即解析器对象parser。

目录

  • 一、根据XML配置文件构建SqlSessionFactory
  • 二、通过SqlSessionFactory创建SqlSession
  • 三、getMapper获取动态代理对象

一、根据XML配置文件构建SqlSessionFactory

一、首先读取类路径下的配置文件,获取其字节输入流。

二、创建SqlSessionFactoryBuilder对象,调用内部的build方法。 factory = new SqlSessionFactoryBuilder().build(in);

三、根据字节输入流创建XMLConfigBuilder即解析器对象parser。 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //根据字节输入流创建XMLConfigBuilder即解析器对象parser
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //返回的Configuration配置对象作为build的参数
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

四、调用parser对象的parse方法, parser.parse() ,该结果将返回一个Configuration配置对象,作为build方法的参数。

五、parse()方法中,调用parseConfiguration方法将Configuration元素下的所有配置信息封装进Parser对象的成员Configuration对象之中。

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
      //将configuration的配置信息一一封装到configuration中
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

六、其中进行解析xml元素的方式是将通过evalNode方法获取对应名称的节点信息。如: parseConfiguration(parser.evalNode("/configuration")); ,此时 parser.evalNode("/configuration") 即为Configuration下的所有信息。

七、parseConfiguration方法相当于将里面每个元素的信息都单独封装到Configuration中。

mybatis源码学习:从SqlSessionFactory到代理对象的生成

值得一提的是,我们之后要分析基于代理模式产生dao的代理对象涉及到mappers的封装,其实也在配置文件读取封装的时候就已经完成,也就是在parseConfiguration方法之中: mapperElement(root.evalNode("mappers")); 。他的作用就是,读取我们主配置文件中 <mappers> 的元素内容,也就是我们配置的映射配置文件。

<!-- 配置映射文件的位置 -->
    <mappers>
        <package name="com.smday.dao"></package>
    </mappers>

private void mapperElement(XNode parent) 方法将mappers配置下的信息获取,此处获取我们resources包下的com.smday.dao包名。

mybatis源码学习:从SqlSessionFactory到代理对象的生成

接着就调用了configuration的addMappers方法,其实还是调用的是mapperRegistry。

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

读到这里,我们就会渐渐了解MapperRegistry这个类的职责所在,接着来看,这个类中进行的一些工作,在每次添加mappers的时候,会利用ResolverUtil类查找类路径下的该包名路径下,是否有满足条件的类,如果有的话,就将Class对象添加进去,否则报错。

mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:从SqlSessionFactory到代理对象的生成

紧接着,就到了一步比较重要的部分,当然只是我个人觉得,因为第一遍看的时候,我没有想到,这步居然可以封装许许多多的重要信息,我们来看一看:

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 {
        //将接口类作为键,将MapperProxyFactory作为值存入
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // 在运行解析器之前添加类型十分重要,否则可能会自动尝试绑定映射器解析器
        // 如果类型已知,则不会尝试
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //解析mapper映射文件,封装信息
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

映射配置文件的读取依靠namespace,我们可以通过查看源码发现读取映射配置文件的方法是loadXmlResouce(),所以namespace命名空间至关重要:

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";
      InputStream inputStream = null;
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        //最终解析
        xmlParser.parse();
      }
    }
  }
//xmlPaser.parse()
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //读取映射配置文件信息的主要代码
      configurationElement(parser.evalNode("/mapper"));
      //加载完成将该路径设置进去,防止再次加载
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

mybatis源码学习:从SqlSessionFactory到代理对象的生成

可以看到,对映射文件解析之后,mappedStatements对象中出现了以下内容:

mybatis源码学习:从SqlSessionFactory到代理对象的生成

至此,主配置文件和映射配置文件的配置信息就已经读取完毕。

八、最后依据获得的Configuration对象,创建一个 new DefaultSqlSessionFactory(config)

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

总结:

  • 解析配置文件的信息,并保存在Configuration对象中。

  • 返回包含Configuration的DefaultSqlSession对象。

二、通过SqlSessionFactory创建SqlSession

一、调用SqlSessionFactory对象的openSession方法,其实是调用 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) 方法,通过参数就可以知道,分别是执行器的类型,事务隔离级别和设置是否自动提交,因此,我们就可以得知,我们在创建SqlSession的时候可以指定这些属性。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //获取Environment信息
      final Environment environment = configuration.getEnvironment();
      //获取TransactionFactory信息
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      //创建Transaction对象
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //创建执行器对象Executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //创建DefaultSqlSession对象并返回
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

二、从configuration中获取environment、dataSource和transactionFactory信息,创建事务对象Transaction。

mybatis源码学习:从SqlSessionFactory到代理对象的生成

补充:后续看了一些博客,说是保证executor不为空,因为defaultExecutorType有可能为空。

三、根据配置信息,执行器信息和自动提交信息创建DefaultSqlSession。

三、getMapper获取动态代理对象

下面这句话意思非常明了,就是通过传入接口类型对象,获取接口代理对象。

IUserDao userDao1 = sqlSession1.getMapper(IUserDao.class);

具体的过程如下:

一、首先,调用SqlSession的实现类DefaultSqlSession的getMapper方法,其实是在该方法内调用configuration的getMapper方法,将接口类对象以及当前sqlsession对象传入。

//DefaultSqlSession.java
  @Override
  public <T> T getMapper(Class<T> type) {
    //调用configuration的getMapper
    return configuration.<T>getMapper(type, this);
  }

二、接着调用我们熟悉的mapperRegistry,因为我们知道,在读取配置文件,创建sqlSession的时候,接口类型信息就已经被存入到其内部维护的Map之中。

//Configuration.java
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    
    return mapperRegistry.getMapper(type, sqlSession);
  }

mybatis源码学习:从SqlSessionFactory到代理对象的生成 三、我们来看看getMapper方法具体的实现如何:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //根据传入的类型获取对应的键,也就是这个代理工厂
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //最终返回的是代理工厂产生的一个实例对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

四、紧接着,我们进入MapperProxyFactory,真真实实地发现了创建代理对象的过程。

protected T newInstance(MapperProxy<T> mapperProxy) {
    //创建MapperProxy代理对象
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

mybatis源码学习:从SqlSessionFactory到代理对象的生成


以上所述就是小编给大家介绍的《mybatis源码学习:从SqlSessionFactory到代理对象的生成》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

软技能

软技能

John Sonmez / 王小刚 / 人民邮电出版社 / 2016-7 / 59.00元

这是一本真正从“人”(而非技术也非管理)的角度关注软件开发人员自身发展的书。书中论述的内容既涉及生活习惯,又包括思维方式,凸显技术中“人”的因素,全面讲解软件行业从业人员所需知道的所有“软技能”。本书聚焦于软件开发人员生活的方方面面,从揭秘面试的流程到精耕细作出一份杀手级简历,从创建大受欢迎的博客到打造你,从提高自己工作效率到与如何与“拖延症”做斗争,甚至包括如何投资不动产,如何关注自己的健康。本......一起来看看 《软技能》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具