MyBatis源码:SqlSession的创建

对于单纯的Mybatis应用来说,读取配置文件,开启会话是整个应用启动的第一步

第一步:读取配置文件、创建SqlSessionFactory对象

// 使用mybatis提供的资源工具类,可以加载指定的资源文件并生成一个read
Reader resourceAsReader = Resources.getResourceAsReader("mybatis.xml");
// SqlSessionFactoryBuilder通过上面生成的read构建出一个sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);

重点看一下sqlSessionFactory是如何构建出来的。跟进build 方法

 public SqlSessionFactory build(Reader reader) {
    // 调用自己的其他重载方法,完成sqlSessionFactory对象的创建
    return build(reader, null, null);
 }

再跟

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      /*
          创建xml配置解析器,XMLConfigBuilder 和 XMLMapperBuilder都继承了BaseBuilder
          在实例化XMLConfigBuilder的过程中完成了XMLConfigBuilder内部属性的实例化
            this.configuration.setVariables(props);
            this.parsed = false;
            this.environment = environment;
            this.parser = parser;
           其中parser 的实力 是: new XPathParser(reader, true, props, new XMLMapperEntityResolver())
           具体分析可以进入到方法中去查看

       */
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // 在这个方法中完成xml 文档的解析
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

先来解析一个这个方法的参数,第一个就是上面传递过来的资源类,第二个就是环境设置,配置启用哪一个环境,如:dev 或者 prod等,关于第三个参数properties,我猜测是可以让我们传递一些我们自己的额外的配置的

根据代码顺序,先来看一下XMLConfigBuilder的创建过程,跟进方法 org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(java.io.Reader, java.lang.String, java.util.Properties)

public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    // new XPathParser(reader, true, props, new XMLMapperEntityResolver()) 完成了配置文件到docment 的解析过程
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

跟着来看XPathParser 的初始化过程中做了什么事,跟进到org.apache.ibatis.parsing.XPathParser#XPathParser(java.io.Reader, boolean, java.util.Properties, org.xml.sax.EntityResolver)

public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    // 完成一些属性的实例化
    commonConstructor(validation, variables, entityResolver);
    // 解析最开始通过Resources生成的reader,并创建一个document
    this.document = createDocument(new InputSource(reader));
  }

其中commonConstructor 方法中完成一些属性的初始化,而在下面的createDocument方法中,通过jdk的原生文档构建器构建出一个文档对象,以下是createDocument方法的内容

/**
   * 通过reader创建一个document
   * 通过jdk 原生的文档构建工厂DocumentBuilderFactory 以传入的reader 重建 一个文件构建器,并将传递过来的输入流进行解析生成相应的文档对象
   * @param inputSource
   * @return
   */
  private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      builder.setErrorHandler(new ErrorHandler() {
        @Override
        public void error(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void fatalError(SAXParseException exception) throws SAXException {
          throw exception;
        }

        @Override
        public void warning(SAXParseException exception) throws SAXException {
          // NOP
        }
      });
      return builder.parse(inputSource);
    } catch (Exception e) {
      throw new BuilderException("Error creating document instance.  Cause: " + e, e);
    }
  }

到这里XPathParser的的创建过程就完了,在这个过程中完成了XPathParser对象中的一些属性的初始化工作,并根据最开始传入的配置文件资源类,创建了一个文档对象并赋值给了XPathParserdocument属性。回过头来继续看XMLConfigBuilder的创建过程。

this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);

通过以上代码,可以看出调用了重载的构造方法org.apache.ibatis.builder.xml.XMLConfigBuilder#XMLConfigBuilder(org.apache.ibatis.parsing.XPathParser, java.lang.String, java.util.Properties) 来看一下这个重载的方法中做了什么

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    // 这里初始化了当前类从父类继承的 configuration、typeAliasRegistry、typeHandlerRegistry 这三个属性
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

首先调用了父类的构造方法,并初始化了一个org.apache.ibatis.session.Configuration类对象。
先来看一下Configuration对象的创建过程,这里调用的是它的默认的构造方法,在这个方法里完成了一些默认的别名的注册,一下是具体代码

 public Configuration() {
    // 以下是注册了一些内置的别名
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);
  }

可以看到,在Configuration类的默认构造方法中,主要都是通过typeAliasRegistry来完成别名的注册的。那么接下来,就先来看一下这个typeAliasRegistry 是个什么东西。以下是typeAliasRegistryConfiguration类中初始化的代码

protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

接下来看一下new TypeAliasRegistry();这一段代码中发生了什么

public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

可以看到,在TypeAliasRegistry 初始化的过程中就已经注册了很多的别名,至于注册过程这里就不展开细说了,后面有时间的话再说,但是有一点可以先说一下,那就是这里最终注册的别名最终都会存在一个map中,其中key是别名,value是对应类的Class对象

除了TypeAliasRegistry注册器,其实还有一个注册器TypeHandlerRegistry也很重要,不过他们的初始化过程都差不多,这里就不重复说明了,后面都会都单独的文档进行说明的

好了,回过来再看父类的构造方法,跟进去org.apache.ibatis.builder.BaseBuilder#BaseBuilder

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

很简单,就是对一些属性进行了一次初始化。继续回过头来看XMLConfigBuilder的创建

 // 设置一些额外的配置
this.configuration.setVariables(props);
 // 表示当前配置文件还没有被解析
 this.parsed = false;
 // 设置当前环境
 this.environment = environment;
 // 将上面创建的XPathParser对象赋值给parser 属性
 this.parser = parser;

到这里 XMLConfigBuilder的实例算是创建完成了,通过上面的分析过程可以知晓,在XMLConfigBuilder初始化的过程中根据配置文件生成了一个document对象,但是并没有对这个对象进行解析。所以,接下的任务就是对这个document对象进行解析了

回到 org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader, java.lang.String, java.util.Properties)方法中去,我们可以看到,在完成XMLConfigBuilder的创建后,紧接着执行的就是build(parser.parse())这个方法了,那么毫无疑问,对于document的解析应该是在这个方法中完成的

先来分析parser.parse()这个方法,由于parserXMLConfigBuilder类的实例,那么parse()方法肯定是属于XMLConfigBuilder的,接下来就跟进到org.apache.ibatis.builder.xml.XMLConfigBuilder#parse方法中继续分析

 /**
   * 这里开始解析docment,只解析一次,解析过之后将parsed设置为ture, 下一次判断如果解析过了,直接抛出错误
   * @return
   */
  public Configuration parse() {
  	// 由该类的构造函数可以知道,这个时候parsed的值为false
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    /*
      configuration是mybatis配置文件的根标签
      parser.evalNode()方法的作用是返回节点的org.apache.ibatis.parsing.XNode表示,XNode里面主要把关键的节点属性和占位符变量结构化出来
     */
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

结合上面的注释进行分析,可以知道,在这个方法中真正做事的是下面这句代码,而这句代码也是正式解析mybatis配置文件的开始

 parseConfiguration(parser.evalNode("/configuration"));

先来看parser.evalNode("/configuration"),由上面的分析可以知道,parser正是之前创建的XPathParser。点到evalNode()方法中


  /**
   * 根据传入的表达式解析本类的document属性
   * @param expression
   * @return
   */
  public XNode evalNode(String expression) {
    return evalNode(document, expression);
  }

  public XNode evalNode(Object root, String expression) {
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
      return null;
    }
    return new XNode(this, node, variables);
  }


  private Object evaluate(String expression, Object root, QName returnType) {
    try {
      return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
      throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
  }

可以看到最终的结果是由xpath 根据传入表达式对 XPathParser的自身属性document进行解析,并返回一个Node对象,然后由Xnode进行包装后返回给最终的调用者。

上面所说的 xpath 以及 document都是在 XPathParser的实例化过程中被实例化的,其中xpath是在XPathParser的构造方法中调用的commonConstructor方法中初始化的,而document就是构造方法中初始化的

 public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
    // 完成一些属性的实例化
    commonConstructor(validation, variables, entityResolver);
    // 解析最开始通过Resources生成的reader,并创建一个document
    this.document = createDocument(new InputSource(reader));
  }

现在知道了parser.evalNode("/configuration")返回的事一个Xnode对象,那么就先来看一下XNode 的数据结构,以下是XNode的相关属性。这些都属性将在唯一的构造方法中完成初始化

private final Node node;
  private final String name;
  private final String body;
  private final Properties attributes;
  private final Properties variables;
  private final XPathParser xpathParser
}

回过头来看parseConfiguration(parser.evalNode("/configuration"))中的 parseConfiguration()方法

/**
   * 这里是真正解析xml每个标签的地方
   * @param root
   */
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      // 解析settings 标签,将解析到的配置存放到一个properties中
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      
      loadCustomVfs(settings);
      // 从setting 标签解析出来的结果中设置日志的实现
      loadCustomLogImpl(settings);
      // 解析typeAliases 标签,并将别名以及对应的类型注册到父类定义的org.apache.ibatis.builder.BaseBuilder.typeAliasRegistry
      typeAliasesElement(root.evalNode("typeAliases"));
      // 解析plugins 标签
      pluginElement(root.evalNode("plugins"));
      // 解析objectFactory 标签,加载对象工厂
      //  什么是对象工厂?MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
      // 默认的对象工厂DefaultObjectFactory做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析对象包装器工厂标签,并创建对象包装器工厂objectWrapperFactoryElement
      // 对象包装器工厂主要用来包装返回result对象,比如说可以用来设置某些敏感字段脱敏或者加密等。默认对象包装器工厂是DefaultObjectWrapperFactory,也就是不使用包装器工厂
      // 提到了对象包装器工厂,就要先提一下对象包装器ObjectWrapper,它的默认实现是BeanWrapper
      // 要实现自定义的对象包装器工厂,只要实现ObjectWrapperFactory中的两个接口hasWrapperFor和getWrapperFor
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      /*
      因为加载配置文件中的各种插件类等等,为了提供更好的灵活性,mybatis支持用户自定义反射工厂,不过总体来说,用的不多,要实现反射工厂,
      只要实现ReflectorFactory接口即可。默认的反射工厂是DefaultReflectorFactory。一般来说,使用默认的反射工厂就可以了。
       */
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // 这里将上面解析过的setting标签的内容设置到了configuration中,若未中setting标签中解析到内容,则采用默认值
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //  加载环境配置
      environmentsElement(root.evalNode("environments"));
      /*
       加载数据库厂商标识
        MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
       MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃
       */
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      /*
       加载类型处理器
       无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
      mybatis提供了两种方式注册类型处理器,package自动检索方式和显示定义方式。使用自动检索(autodiscovery)功能的时候,只能通过注解方式来指定 JDBC 的类型。、
      为了简化使用,mybatis在初始化TypeHandlerRegistry期间,自动注册了大部分的常用的类型处理器比如字符串、数字、日期等。对于非标准的类型,用户可以自定义类型处理器来处理。
      要实现一个自定义类型处理器,只要实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个实用类 org.apache.ibatis.type.BaseTypeHandler,
      并将它映射到一个 JDBC 类型即可。
       */
      typeHandlerElement(root.evalNode("typeHandlers"));
      /*
      加载mapper文件
       */
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

初步看一下,可以得出一个结论,在这个方法中将会对mybatis配置文件中的每一个标签进行解析。那么下一篇文档就来一步一步份分析这个解析过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值