MyBatis 初始化原理

MyBatis 是现在比较流行的 ORM 框架,得益于它简单易用,虽然增加了开发者的一些操作,但是带来了设计上的灵活,得到广泛的使用。

MyBatis 框架的设计分层非常清晰,主要分成三层:

  • 接口层,暴露给开发者使用。
  • 核心数据处理层,实现 MyBatis 内部处理逻辑和流程。
  • 基础模块支撑层,提供通用的模块功能,例如事物管理、连接池管理、缓存、反射等功能。
    mybatis架构

MyBatis 初始化的过程,主要是创建 Configuration 对象的过程。有以下两种方式:

  1. 基于XML配置文件:基于 XML 配置文件的方式是将 MyBatis 的所有配置信息放在 XML 文件中,MyBatis 通过加载并 XML 配置文件,将配置文信息组装成内部的 Configuration 对象。

  2. 基于 Java API :这种方式不使用 XML 配置文件,需要 MyBatis 使用者在 Java 代码中,手动创建 Configuration 对象,然后将配置参数 set 进入 Configuration 对象中。

本文主要基于XML配置文件方式对 MyBatis 框架的初始化原理进行解读,深入理解框架的实现原理。我们将通过以下几点详细介绍 MyBatis 的初始化过程。

  • 创建一个简单的 MyBatis Java 项目
  • MyBatis 基于 XML 配置文件创建 Configuration 对象的过程
  • 涉及到的设计模式

一、创建一个简单的 MyBatis Java 项目

先创建一个简单的Java项目。

1.1 User 用户实体类

public class SysUser {
	private Long id;
	private String userName;
	private String userPassword;
	private String userEmail;
	// PS:省略setter、getter函数
}

1.2 UserMapper 用户持久化接口

public interface UserMapper {
	SysUser selectById(Long id);
}

1.3 UserMapper.xml 产品映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hyc.test.mapper.UserMapper">
    <select id="selectById" resultType="com.hyc.test.model.SysUser">
        select * from sys_user where id= #{id}
    </select>
</mapper>

1.4 MyBatis的配置文件 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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC">
                <property name="" value=""/>
            </transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com\hyc\test\mapper\UserMapper.xml"></mapper>
    </mappers>
</configuration>

1.5 Test 主函数

public class Test {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            User user = userMapper.selectById(1L);
            Assert.assertNotNull(user);
            Assert.assertEquals("test", user.getUserName());
        } finally {
            sqlSession.close();
            reader.close();
        }
    }
}

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

MyBatis 使用 org.apache.ibatis.session.Configuration 对象作为一个存储所有配置信息的容器,Configuration 对象的属性和 mybatis-config.xml 配置文件的组织结构几乎完全一样(当然,Configuration 对象的功能并不限于此,它还负责创建一些 MyBatis 内部使用的对象,如 Executor 等,具体执行器不在本文中讨论)。

MyBatis初始化基本过程:

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

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

1. 读取配置文件 

2. 调用 SqlSessionFactoryBuilder 对象的 build(reader) 方法;

3. SqlSessionFactoryBuilder 会根据输入 reader 等信息创建 XMLConfigBuilder 对象;

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

5. XMLConfigBuilder 对象返回 Configuration 对象;

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

7. SqlSessionFactoryBuilder 返回 DefaultSessionFactory 对象给 Client,供 Client 使用。
读取配置文件

从 Test 主函数第一步可以看出,首先读取 mybatis-config.xml 解析成 Reader。

	String resource = "mybatis-config.xml";
	Reader reader = Resources.getResourceAsReader(resource);
创建 SqlSessionFactoryBuilder 对象

从 SqlSessionFactoryBuilder 的名字中可以看出,SqlSessionFactoryBuilder 是用来创建 SqlSessionFactory 对象的。
从 SqlSessionFactoryBuilder 源码可以看出,SqlSessionFactoryBuilder 中只有一些重载的 build 函数,这些 build 函数的入参都是 MyBatis 配置文件的输入流,返回值都是 SqlSessionFactory;由此可见,SqlSessionFactoryBuilder 的作用很纯粹,就是用来通过配置文件创建 SqlSessionFactory 对象的。

public class SqlSessionFactoryBuilder {

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

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

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

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      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.
      }
    }
  }

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      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.
      }
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}
SqlSessionFactory 创建过程

下面是具体如何创建 SqlSessionFactory 对象的 build 函数,通过构造 XMLConfigBuilder 对象,利用其 parse() 方法返回的 Configuration 对象,构建DefaultSqlSessionFactory 对象。

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        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.
        }
    }
}
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}
创建Configuration对象的过程

以上是整个创建 SqlSessionFactory 的流程,而从上面可以看出创建 Configuration 对象,主要是通过 XMLConfigBuilder 对象,调用其 parse() 方法返回 Configuration 对象。

构造 XMLConfigBuilder 对象

SqlSessionFactoryBuilder 的 build 函数首先会构造一个 XMLConfigBuilder 对象,该对象是用来解析XML配置文件的。XMLConfigBuilder 的继承自 BaseBuilder, BaseBuilder 有 XMLxxxBuilder 子类,这些 XMLxxxBuilder 是用来解析 XML 配置文件的,不同类型 XMLxxxBuilder 用来解析 MyBatis 配置文件的不同部位。比如:XMLConfigBuilder 用来解析 MyBatis 的配置文件, XMLMapperBuilder 用来解析MyBatis中的映射文件(如上文提到的 UserMapper.xml),XMLStatementBuilder用来解析映射文件中的SQL语句。

当创建 XMLConfigBuilder 对象时,就会初始化 Configuration 对象,并且在初始化 Configuration 对象的时候,一些别名会被注册到 Configuration 的 typeAliasRegistry 容器中。

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;
    }
    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);
    ……
}
解析配置文件

当有了 XMLConfigBuilder 对象之后,接下来就可以用它来解析配置文件了。

public Configuration parse() {
	if (parsed) {
		throw new BuilderException("Each XMLConfigBuilder can only be used once.");
	}
	parsed = true;
	parseConfiguration(parser.evalNode("/configuration"));
	return configuration;
}
private void parseConfiguration(XNode root) {
	try {
		// 解析<properties>节点
		propertiesElement(root.evalNode("properties"));
		// 解析<settings>节点
		Properties settings = settingsAsProperties(root.evalNode("settings"));
		loadCustomVfs(settings);
		// 解析<typeAliases>节点
		typeAliasesElement(root.evalNode("typeAliases"));
		// 解析<plugins>节点
		pluginElement(root.evalNode("plugins"));
		// 解析<objectFactory>节点
		objectFactoryElement(root.evalNode("objectFactory"));
		objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
		// 解析<reflectorFactory>节点
			reflectorFactoryElement(root.evalNode("reflectorFactory"));
		settingsElement(settings);
		// 解析<environments>节点
		environmentsElement(root.evalNode("environments"));
		databaseIdProviderElement(root.evalNode("databaseIdProvider"));
		typeHandlerElement(root.evalNode("typeHandlers"));
		// 解析<mappers>节点
		mapperElement(root.evalNode("mappers"));
	} catch (Exception e) {
		throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
	}
}

从上述代码中可以看到,XMLConfigBuilder 会依次解析配置文件中的 properties、settings、environments、typeAliases、plugins、mappers 等属性。具体的解析细节不在本文展开,主要是 XMLConfigBuilder 将 XML 配置文件的信息转换为 Document 对象,而 XML 配置定义文件 DTD 转换成 XMLMapperEntityResolver 对象,然后将封装到 XpathParser 对象中,XpathParser 的作用是提供根据 Xpath 表达式获取基本的 DOM 节点 Node 信息的操作,而解析 mappers 节点还使用到了前面提到了 XMLMapperBuilder。

上述的MyBatis初始化基本过程的序列图细化。

ConfigurationInit

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

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

Configuration :该对象是 mybatis-config.xml 文件中所有 MyBatis 配置信息;

SqlSessionFactory:SqlSession 工厂类,以工厂形式创建 SqlSession 对象,采用了工厂设计模式;

XMLConfigBuilder :负责将 mybatis-config.xml 配置文件解析成 Configuration 对象,供 SqlSessonFactoryBuilder 使用,创建 SqlSessionFactory。

以上就是 MyBatis 初始化创建 SqlSessionFactory 完整流程。

参考:

更新时间:2020-12-13 13:15:46

本文由 HycJack 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.hycjack.cn/archives/myabtis01md
最后更新:2020-12-13 13:15:46

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×