欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MyBatis原理分析之三:初始化(配置文件读取与解析)

程序员文章站 2022-05-23 16:24:32
...

Mybatis的初始化过程,就是组装Configuration的过程,主要分为系统环境参数初始化Mapper映射初始化,其中Mapper映射初始化尤为重要。

先看一个配置文件mybatis-config.xml,里面的内容大致为:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
	<properties resource="jdbc.properties">
		<property name="username" value="root" />
		<property name="password" value="123" />
	</properties>
	<settings>
		<setting name="localCacheScope" value="STATEMENT"/>
		<setting name="cacheEnabled" value="false" />
		<setting name="lazyLoadingEnabled" value="true" />
		<setting name="multipleResultSetsEnabled" value="true" />
		<setting name="useColumnLabel" value="true" />
		<setting name="useGeneratedKeys" value="false" />
		<setting name="defaultExecutorType" value="REUSE" />
		<setting name="defaultStatementTimeout" value="25000" />
	</settings>
	<typeAliases>
		<typeAlias alias="Student" type="com.mybatis3.domain.Student" />
		<typeAlias alias="Teacher" type="com.mybatis3.domain.Teacher" />
	</typeAliases>
	<typeHandlers>
		<typeHandler handler="com.mybatis3.typehandlers.PhoneTypeHandler" />
	</typeHandlers>
	<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="${driver}" />
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
			</dataSource>
		</environment>
	</environments>
	<mappers>
		<mapper resource="com/mybatis3/mappers/StudentMapper.xml" />
		<mapper resource="com/mybatis3/mappers/TeacherMapper.xml" />
	</mappers>
</configuration>

1.测试初始化主要代码

String resource = "mybatis.cfg.xml";  //第一行
  
Reader reader = Resources.getResourceAsReader(resource);  //第二行
  
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);  //第三行
  
SqlSession session = ssf.openSession();  //第四行

2.源码分析

首先,看看第一行和第二行代码,看看主要完成了什么事。

String resource = "mybatis.cfg.xml";  
  
Reader reader = Resources.getResourceAsReader(resource);  

读取Mybaits的主配置配置文件,并返回该文件的输入流,我们知道Mybatis所有的SQL语句都写在XML配置文件里面,所以第一步就需要读取这些XML配置文件,这个不难理解,关键是读取文件后怎么存放。

我们接着看第三行代码(如下),该代码主要是读取配置文件流并将这些配置信息存放到Configuration类中。

SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);  

SqlSessionFactoryBuilder的build的方法如下:

 public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

其实是调用该类的另一个build方法来执行的,具体代码如下:

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      //parser.parse()返回的就是目标Configuration对象
      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.
      }
    }
  }

我们重点看一下里面两行:

//创建一个配置文件流的解析对象XMLConfigBuilder,其实这里是将环境和配置文件流赋予解析类  
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 解析类对配置文件进行解析并将解析的内容存放到Configuration对象中,并返回SqlSessionFactory
 return build(parser.parse());

这里的XMLConfigBuilder初始化其实调用的代码如下:

//XPathParser 是用来解析xml文件的 包括检测xml文件格式
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

 XMLConfigBuilder的parse方法执行代码如下:  

 public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

解析的内容主要是在parseConfiguration方法中,它主要完成的工作是读取配置文件的各个节点,然后将这些数据映射到内存配置对象Configuration中,我们看一下parseConfiguration方法内容: 

private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      //重点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

以上代码,对mybatis-config.xml配置文件内的元素,使用XPath(XPath即为XML路径语言,它是一种用来确定XML(标准通用标记语言的子集)文档中某部分位置的语言。)进行逐一读取。

Xml文件元素和Configuration属性映射表(可查看源码中Configuratio类):

<properties>元素:Properties variables。

<settings>元素:Integer defaultStatementTimeout、Integer defaultFetchSize、ExecutorTypedefaultExecutorType……

<typeAliases>元素:TypeAliasRegistry typeAliasRegistry。

<typeHandlers>元素:TypeHandlerRegistry typeHandlerRegistry。

<environments>元素:Environment environment。配置多个<environment>元素时,Mybatis只会读取默认的那一个。

<mappers>元素:MapperRegistry mapperRegistry。

Mapper映射初始化是我们关注的重点,即mapperElement(root.evalNode("mappers"))方法。

循环处理mapperElement(root.evalNode("mappers"))的结果。主要看下每个循环体中,

org.apache.ibatis.builder.xml.XMLMapperBuilder.parse()方法源码。

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

逐一读取Mapper.xml文件内的各个元素。为了更为直观的了解xml元素至Mybatis的内部数据结构,我做了一个对照图。

MyBatis原理分析之三:初始化(配置文件读取与解析)

这些Xml配置元素,Mybatis将它们分别封装成了ParameterMap、ParameterMapping、ResultMap、ResultMapping、MappedStatement、BoundSql等内部数据结构对象。

这些数据库结构对象,均放置于Configuration内部保存起来。

protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");

 最后的build方法其实是传入配置对象进去,创建DefaultSqlSessionFactory实例出来. DefaultSqlSessionFactory是SqlSessionFactory的默认实现. 

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

 最后我们看一下第四行代码: 

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

 下面我们看一下openSessionFromDataSource方法的逻辑: 

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      ////获取配置信息里面的环境信息,这些环境信息都是包括使用哪种数据库,连接数据库的信息,事务  
      final Environment environment = configuration.getEnvironment();
      ////根据环境信息关于事务的配置获取事务工厂  
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     //从事务工厂获取一个事务实例  
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      ////从配置信息中获取一个执行器实例  
      final Executor executor = configuration.newExecutor(tx, execType);
      //返回SqlSession的一个默认实例
      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();
    }
  }

传入参数说明:

1ExecutorType:执行类型,ExecutorType主要有三种类型:SIMPLE, REUSE, BATCH,默认是SIMPLE,都在枚举类ExecutorType里面。

2TransactionIsolationLevel:事务隔离级别,都在枚举类TransactionIsolationLevel中定义。

3autoCommit:是否自动提交,主要是事务提交的设置。

 DefaultSqlSession是SqlSession的实现类,该类主要提供操作数据库的方法给开发人员使用。

3.总结过程

1.读取Ibatis的主配置文件,并将文件读成文件流形式(InputStream)。

2.从主配置文件流中读取文件的各个节点信息并存放到Configuration对象中。读取mappers节点的引用文件,并将这些文件的各个节点信息存放到Configuration对象。

3.根据Configuration对象的信息获取数据库连接,并设置连接的事务隔离级别等信息,将经过包装数据库连接对象SqlSession接口返回,DefaultSqlSession是SqlSession的实现类,所以这里返回的是DefaultSqlSession,SqlSession接口里面就是对外提供的各种数据库操作。

相关标签: MyBatis初始化

上一篇: 集合

下一篇: MyBatis框架学习-3