zoukankan      html  css  js  c++  java
  • 深入理解Mybatis中sqlSessionFactory机制原理

    对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外。本章将通过以下几点详细介绍MyBatis的初始化过程。

        1.MyBatis的初始化做了什么

        2. MyBatis基于XML配置文件创建Configuration对象的过程

        3. 手动加载XML配置文件创建Configuration对象完成初始化,创建并使用SqlSessionFactory对象

        4. 涉及到的设计模式

    一、 MyBatis的初始化做了什么

        任何框架的初始化,无非是加载自己运行时所需要的配置信息。MyBatis的配置信息,大概包含以下信息,其高层级结构如下:

    × configuration 配置

        × properties 属性

        × settings 设置

        × typeAliases 类型命名

        × typeHandlers 类型处理器

        × objectFactory 对象工厂

        × plugins 插件

        × environments 环境

            × environment 环境变量

            × transactionManager 事务管理器

            × dataSource 数据源

    × 映射器

    MyBatis的上述配置信息会配置在XML配置文件中,那么,这些信息被加载进入MyBatis内部,MyBatis是怎样维护的呢?

    MyBatis采用了一个非常直白和简单的方式---使用org.apache.ibatis.session.Configuration 对象作为一个所有配置信息的容器,Configuration对象的组织结构和XML配置文件的组织结构几乎完全一样(当然,Configuration对象的功能并不限于此,它还负责创建一些MyBatis内部使用的对象,如Executor等,这将在后续的文章中讨论)。如下图所示:

    MyBatis根据初始化好Configuration信息,这时候用户就可以使用MyBatis进行数据库操作了。

    可以这么说,MyBatis初始化的过程,就是创建 Configuration对象的过程。

    MyBatis的初始化可以有两种方式:

    • 基于XML配置文件:基于XML配置文件的方式是将MyBatis的所有配置信息放在XML文件中,MyBatis通过加载并XML配置文件,将配置文信息组装成内部的Configuration对象
    • 基于Java API:这种方式不使用XML配置文件,需要MyBatis使用者在Java代码中,手动创建Configuration对象,然后将配置参数set 进入Configuration对象中 

    (PS:  MyBatis具体配置信息有哪些,又分别表示什么意思,不在本文的叙述范围,读者可以参考我的 《Java Persistence withMyBatis 3 (中文版)》 的第二章 引导MyBatis 中有详细的描述)

    接下来我们将通过 基于XML配置文件方式的MyBatis初始化,深入探讨MyBatis是如何通过配置文件构建Configuration对象,并使用它的。

    二、MyBatis基于XML配置文件创建Configuration对象的过程

    现在就从使用MyBatis的简单例子入手,深入分析一下MyBatis是怎样完成初始化的,都初始化了什么。看以下代码:

    1.  
      String resource = "mybatis-config.xml";
    2.  
      InputStream inputStream = Resources.getResourceAsStream(resource);
    3.  
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    4.  
      SqlSession sqlSession = sqlSessionFactory.openSession();
    5.  
      List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

    有过MyBatis使用经验的读者会知道,上述语句的作用是执行com.foo.bean.BlogMapper.queryAllBlogInfo 定义的SQL语句,返回一个List结果集。总的来说,上述代码经历了 mybatis初始化 -->创建SqlSession -->执行SQL语句返回结果三个过程。

        上述代码的功能是根据配置文件mybatis-config.xml  配置文件,创建SqlSessionFactory对象,然后产生SqlSession,执行SQL语句。而mybatis的初始化就发生在第三句: SqlSessionFactory sqlSessionFactory =  new SqlSessionFactoryBuilder().build(inputStream);   现在就让我们看看第三句到底发生了什么。

    MyBatis 初始化基本过程: 

    SqlSessionFactoryBuilder根据传入的数据流生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实例。

    初始化的基本过程如下序列图所示:

        

    由上图所示, mybatis 初始化要经过简单的以下几步:

    1.  调用 SqlSessionFactoryBuilder 对象的 build(inputStream) 方法;

    2.  SqlSessionFactoryBuilder 会根据输入流 inputStream 等信息创建XMLConfigBuilder 对象 ;

    3.  SqlSessionFactoryBuilder 调用 XMLConfigBuilder 对象的 parse() 方法;

    4.  XMLConfigBuilder 对象返回 Configuration 对象;

    5.  SqlSessionFactoryBuilder 根据 Configuration 对象创建一个DefaultSessionFactory 对象;

    6.  SqlSessionFactoryBuilder 返回 DefaultSessionFactory 对象给 Client ,供 Client使用。

    SqlSessionFactoryBuilder 相关的代码如下所示:

    public SqlSessionFactory build(InputStream inputStream)
    {
      return build(inputStream, null, null);
    }
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
    {
      try
      {
        //2. 创建XMLConfigBuilder对象用来解析XML配置文件,生成Configuration对象
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //3. 将XML配置文件内的信息解析成Java对象Configuration对象
        Configuration config = parser.parse();
        //4. 根据Configuration对象创建出SqlSessionFactory对象
        return build(config);
      }
      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.
        }
      }
    }
    //从此处可以看出,MyBatis内部通过Configuration对象来创建SqlSessionFactory,用户也可以自己通过API构造好Configuration对象,调用此方法创建SqlSessionFactory
    public SqlSessionFactory build(Configuration config)
    {
      return new DefaultSqlSessionFactory(config);
    }

    上述的初始化过程中,涉及到了以下几个对象:

    • SqlSessionFactoryBuilder : SqlSessionFactory的构造器,用于创建SqlSessionFactory,采用了Builder设计模式

    • Configuration :该对象是mybatis-config.xml文件中所有mybatis配置信息
    • SqlSessionFactory:SqlSession工厂类,以工厂形式创建SqlSession对象,采用了Factory工厂设计模式
    • XmlConfigParser :负责将mybatis-config.xml配置文件解析成Configuration对象,共SqlSessonFactoryBuilder使用,创建SqlSessionFactory

    创建Configuration对象的过程

    接着上述的 MyBatis初始化基本过程讨论,当SqlSessionFactoryBuilder执行build()方法,调用了XMLConfigBuilder的parse()方法,然后返回了Configuration对象。那么parse()方法是如何处理XML文件,生成Configuration对象的呢?

    1. XMLConfigBuilder会将XML配置文件的信息转换为Document对象,而XML配置定义文件DTD转换成XMLMapperEntityResolver对象,然后将二者封装到XpathParser对象中,XpathParser的作用是 提供根据Xpath表达式获取基本的DOM节点Node信息的操作 。如下图所示:

    2.  之后XMLConfigBuilder调用parse()方法:会从XPathParser中取出 <configuration>节点对应的Node对象,然后解析此Node节点的子Node:properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers

    public Configuration parse()
        {
            if (parsed)
            {
                throw new BuilderException("Each XMLConfigBuilder can only be used once.");
            }
            parsed = true;
            //源码中没有这一句,只有 parseConfiguration(parser.evalNode("/configuration"));
            //为了让读者看得更明晰,源码拆分为以下两句
            XNode configurationNode = parser.evalNode("/configuration");
            parseConfiguration(configurationNode);
            return configuration;
        }
        /*
        解析 "/configuration"节点下的子节点信息,然后将解析的结果设置到Configuration对象中
        */
      private void parseConfiguration(XNode root) {
        try {
          //1.首先处理properties 节点	
          propertiesElement(root.evalNode("properties")); //issue #117 read properties first
          //2.处理typeAliases
          typeAliasesElement(root.evalNode("typeAliases"));
          //3.处理插件
          pluginElement(root.evalNode("plugins"));
          //4.处理objectFactory
          objectFactoryElement(root.evalNode("objectFactory"));
          //5.objectWrapperFactory
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          //6.settings
          settingsElement(root.evalNode("settings"));
          //7.处理environments
          environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
          //8.database
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          //9. typeHandlers
          typeHandlerElement(root.evalNode("typeHandlers"));
          //10 mappers
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }

    注意:在上述代码中,还有一个非常重要的地方,就是解析XML配置文件子节点<mappers>的方法 mapperElements(root.evalNode("mappers")) , 它将解析我们配置的Mapper.xml配置文件,Mapper配置文件可以说是MyBatis的核心,MyBatis的特性和理念都体现在此Mapper的配置和设计上,我们将在后续的文章中讨论它,敬请期待~

    3.  然后将这些值解析出来设置到Configuration对象中。

    解析子节点的过程这里就不一一介绍了,用户可以参照MyBatis源码仔细揣摩,我们就看上述的 environmentsElement(root.evalNode( "environments" ));  方法是如何将environments 的信息解析出来,设置到 Configuration 对象中的:

    1.  
      /*
    2.  
      解析environments节点,并将结果设置到Configuration对象中
    3.  
      注意:创建envronment时,如果SqlSessionFactoryBuilder指定了特定的环境(即数据源);
    4.  
      则返回指定环境(数据源)的Environment对象,否则返回默认的Environment对象;
    5.  
      这种方式实现了MyBatis可以连接多数据源
    6.  
      */
    7.  
      private void environmentsElement(XNode context) throws Exception
    8.  
      {
    9.  
      if (context != null)
    10.  
      {
    11.  
      if (environment == null)
    12.  
      {
    13.  
      environment = context.getStringAttribute("default");
    14.  
      }
    15.  
      for (XNode child : context.getChildren())
    16.  
      {
    17.  
      String id = child.getStringAttribute("id");
    18.  
      if (isSpecifiedEnvironment(id))
    19.  
      {
    20.  
      //1.创建事务工厂 TransactionFactory
    21.  
      TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
    22.  
      DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
    23.  
      //2.创建数据源DataSource
    24.  
      DataSource dataSource = dsFactory.getDataSource();
    25.  
      //3. 构造Environment对象
    26.  
      Environment.Builder environmentBuilder = new Environment.Builder(id)
    27.  
      .transactionFactory(txFactory)
    28.  
      .dataSource(dataSource);
    29.  
      //4. 将创建的Envronment对象设置到configuration 对象中
    30.  
      configuration.setEnvironment(environmentBuilder.build());
    31.  
      }
    32.  
      }
    33.  
      }
    34.  
      }
    35.  
      private boolean isSpecifiedEnvironment(String id)
    36.  
      {
    37.  
      if (environment == null)
    38.  
      {
    39.  
      throw new BuilderException("No environment specified.");
    40.  
      }
    41.  
      else if (id == null)
    42.  
      {
    43.  
      throw new BuilderException("Environment requires an id attribute.");
    44.  
      }
    45.  
      else if (environment.equals(id))
    46.  
      {
    47.  
      return true;
    48.  
      }
    49.  
      return false;
    50.  
      }

    4.  返回Configuration对象

    我们将上述的MyBatis初始化基本过程的序列图细化,

    三、手动加载XML配置文件创建Configuration对象完成初始化,创建并使用SqlSessionFactory对象

    我们可以使用XMLConfigBuilder手动解析XML配置文件来创建Configuration对象,代码如下:

    1.  
      String resource = "mybatis-config.xml";
    2.  
      InputStream inputStream = Resources.getResourceAsStream(resource);
    3.  
      //手动创建XMLConfigBuilder,并解析创建Configuration对象
    4.  
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null,null);
    5.  
      Configuration configuration=parse();
    6.  
      //使用Configuration对象创建SqlSessionFactory
    7.  
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
    8.  
      //使用MyBatis
    9.  
      SqlSession sqlSession = sqlSessionFactory.openSession();
    10.  
      List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

    四、涉及到的设计模式

    初始化的过程涉及到创建各种对象,所以会使用一些 创建型的设计模式 。在初始化的过程中,Builder模式运用的比较多。

    Builder模式应用1: SqlSessionFactory的创建

    对于创建SqlSessionFactory时,会根据情况提供不同的参数,其参数组合可以有以下几种:

    由于构造时参数不定,可以为其创建一个构造器Builder,将SqlSessionFactory的构建过程和表示分开:

    MyBatis将SqlSessionFactoryBuilder和SqlSessionFactory相互独立。

    Builder模式应用2: 数据库连接环境Environment对象的创建

    在构建Configuration对象的过程中,XMLConfigParser解析 mybatis XML配置文件节点<environment>节点时,会有以下相应的代码:

    1.  
      private void environmentsElement(XNode context) throws Exception {
    2.  
      if (context != null) {
    3.  
      if (environment == null) {
    4.  
      environment = context.getStringAttribute("default");
    5.  
      }
    6.  
      for (XNode child : context.getChildren()) {
    7.  
      String id = child.getStringAttribute("id");
    8.  
      //是和默认的环境相同时,解析之
    9.  
      if (isSpecifiedEnvironment(id)) {
    10.  
      TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
    11.  
      DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
    12.  
      DataSource dataSource = dsFactory.getDataSource();
    13.  
       
    14.  
      //使用了Environment内置的构造器Builder,传递id 事务工厂和数据源
    15.  
      Environment.Builder environmentBuilder = new Environment.Builder(id)
    16.  
      .transactionFactory(txFactory)
    17.  
      .dataSource(dataSource);
    18.  
      configuration.setEnvironment(environmentBuilder.build());
    19.  
      }
    20.  
      }
    21.  
      }
    22.  
      }

    在Environment内部,定义了静态内部Builder类:

    1.  
      public final class Environment {
    2.  
      private final String id;
    3.  
      private final TransactionFactory transactionFactory;
    4.  
      private final DataSource dataSource;
    5.  
       
    6.  
      public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    7.  
      if (id == null) {
    8.  
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    9.  
      }
    10.  
      if (transactionFactory == null) {
    11.  
      throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    12.  
      }
    13.  
      this.id = id;
    14.  
      if (dataSource == null) {
    15.  
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    16.  
      }
    17.  
      this.transactionFactory = transactionFactory;
    18.  
      this.dataSource = dataSource;
    19.  
      }
    20.  
       
    21.  
      public static class Builder {
    22.  
      private String id;
    23.  
      private TransactionFactory transactionFactory;
    24.  
      private DataSource dataSource;
    25.  
       
    26.  
      public Builder(String id) {
    27.  
      this.id = id;
    28.  
      }
    29.  
       
    30.  
      public Builder transactionFactory(TransactionFactory transactionFactory) {
    31.  
      this.transactionFactory = transactionFactory;
    32.  
      return this;
    33.  
      }
    34.  
       
    35.  
      public Builder dataSource(DataSource dataSource) {
    36.  
      this.dataSource = dataSource;
    37.  
      return this;
    38.  
      }
    39.  
       
    40.  
      public String id() {
    41.  
      return this.id;
    42.  
      }
    43.  
       
    44.  
      public Environment build() {
    45.  
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    46.  
      }
    47.  
       
    48.  
      }
    49.  
       
    50.  
      public String getId() {
    51.  
      return this.id;
    52.  
      }
    53.  
       
    54.  
      public TransactionFactory getTransactionFactory() {
    55.  
      return this.transactionFactory;
    56.  
      }
    57.  
       
    58.  
      public DataSource getDataSource() {
    59.  
      return this.dataSource;
    60.  
      }
    61.  
       
    62.  
      }
  • 相关阅读:
    POJ 2251 Dungeon Master
    HDU 3085 Nightmare Ⅱ
    CodeForces 1060 B Maximum Sum of Digits
    HDU 1166 敌兵布阵(树状数组)
    HDOJ 2050 折线分割平面
    HDU 5879 Cure
    HDU 1878 欧拉回路
    HDU 6225 Little Boxes
    ZOJ 2971 Give Me the Number
    HDU 2680 Choose the best route
  • 原文地址:https://www.cnblogs.com/borter/p/9569985.html
Copyright © 2011-2022 走看看