搜 索

MyBatis丨初始化流程分析

  • 149阅读
  • 2022年02月08日
  • 0评论
首页 / 默认分类 / 正文

前言

任何框架的初始化,无非都是加载自己运行时所需要的配置信息,MyBatis 也不例外。那究竟是如何加载 XML 配置文件的呢,现在来探究一下。

主要流程

按照 MyBatis 的使用流程来看,大体上可以划分为以下三步。

  1. 加载配置文件,初始化 SqlSessionFactory;
  2. 获取 SqlSession 和 Mapper;
  3. 执行 Mapper 方法。

而第 1 步和第 2 步可以封装为一个工具类:

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @author yyt
 * @date 2020年02月04日 14:31
 * 工具类
 */
public class MyBatisUtil {

    private static SqlSessionFactory sqlSessionFactory;

    // 获取 SqlSessionFactory 的实例
    static {
        try {
            String resource = "config/mybatis-config.xml";
            // 读取 XML 核心配置文件得到输入流对象
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从 SqlSessionFactory 中获取 SqlSession
     *
     * @return org.apache.ibatis.session.SqlSession
     * @author yyt
     * @date 2020/2/4 14:42
     */
    public static SqlSession getSqlSession() {
        // 设置 autoCommit 为 true,自动提交事务
        return sqlSessionFactory.openSession(true);
    }
}

构建 SqlSessionFactory

SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

String resource = "config/mybatis-config.xml";
// 读取 XML 核心配置文件得到输入流对象
InputStream inputStream = Resources.getResourceAsStream(resource);

MyBatis 包含一个名叫 Resources 的工具类,调用它的 getResourceAsStream() 的方法,加载配置 MyBatis 的核心配置文件,得到一个输入流对象。查看 Resources 类的源码中对应的方法:
Resources.class
可以看到在该方法内部调用了同名的重载方法,其中得到输入流对象的是这一句:

InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);

查看完整的源码可知,ClassLoaderWrapper 类中有一个方法 getClassLoaders(ClassLoader classLoader),返回结果为一个 ClassLoader[] 数组,该数组指明了类加载器的使用顺序。查看 ClassLoaderWrapper 类的源码中对应的方法:

public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return this.getResourceAsStream(resource, this.getClassLoaders(classLoader));
}

ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        // 参数传入的指定的默认类加载器,这里传入 null
        classLoader, 
        // 系统指定的默认类加载器
        this.defaultClassLoader,
        // 当前线程绑定的类加载器
        Thread.currentThread().getContextClassLoader(), 
        // 当前类所使用的类加载器
        this.getClass().getClassLoader(),
        // 系统类加载器
        this.systemClassLoader
    };
}

在对应的 getResourceAsStream() 方法中可以看到,它的底层是调用了 java.lang 包下的类加载器 ClassLoader 的 getResourceAsStream(String name) 方法。
ClassLoaderWrapper.class
得到了输入流对象 inputStream,接着往下执行:

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

首先是 new 一个 SqlSessionFactoryBuilder 对象,然后调用它的 build(inputStream) 方法。
点击查看 SqlSessionFactoryBuilder 类中对应的 build(InputStream inputStream) 方法:
SqlSessionFactoryBuilder.class
MyBatis 会先去创建一个 XMLConfigBuilder 解析器对象,去解析传进来的那个输入流对象,解析完成之后,返回一个 Configuration 对象,这个对象里面封装了解析之后我们配置的信息。
继续点击查看 XMLConfigBuilder 类对应的构造方法:
XMLConfigBuilder.class
SqlSessionFactoryBuilder 调用 XMLConfigBuilder 对象的 parse() 方法,在没有解析过的前提下,会调用 parseConfiguration(XNode root) 方法去完成 Configuration 对象的构建,这个 Configuration 对象并不是 XMLConfigBuilder 的属性,而是父类 BaseBuilder 抽象类的 protected 属性。

private void parseConfiguration(XNode root) {
    try {
        // 解析 XML 文件中的标签顺序和配置的顺序一致
        this.propertiesElement(root.evalNode("properties"));
        // 将 settings 标签的配置转换为 Properties 标签
        Properties settings = this.settingsAsProperties(root.evalNode("settings"));
        // 根据配置设置访问资源文件的方式
        this.loadCustomVfs(settings);
        // 根据配置设置日志处理的方式
        this.loadCustomLogImpl(settings);
        // 别名
        this.typeAliasesElement(root.evalNode("typeAliases"));
        // 插件
        this.pluginElement(root.evalNode("plugins"));
        // 对象工厂
        this.objectFactoryElement(root.evalNode("objectFactory"));
        // 对象包装工厂
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        // 反射工厂
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // 通过 settings 配置初始化全局配置
        this.settingsElement(settings);
        // 多环境配置
        this.environmentsElement(root.evalNode("environments"));
        // 数据库类型标志
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 类型转换器
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        // 注册 Mapper XML 映射文件
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}

限于篇幅,这里不展开详细说明如何创建 XNode 对象以及如何使用 XPath 表达式解析节点。

在 XMLConfigBuilder 对象执行 parse() 方法返回 Configuration 对象后,SqlSessionFactoryBuilder 根据这个 Configuration 对象执行了 build(Configuration config) 方法,创建一个 DefaultSessionFactory 对象:

// 返回一个实现了 SqlSessionFactory 接口的 DefaultSqlSessionFactory 对象
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

所以,SqlSessionFactoryBuilder 返回一个 DefaultSessionFactory 对象给 Client,供 Client 使用。

获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,我们可以通过调用 openSession() 方法从中获得 SqlSession 的实例。在这个过程中需要通过 TransactionFactory 生成 Transaction 对象,并且还需要创建核心执行器 Executor 对象。基于这些条件,最终创建了实现 SqlSession 接口的 DefaultSqlSession 对象。

public static SqlSession getSqlSession() {
    // 设置 autoCommit 为 true,自动提交事务
    return sqlSessionFactory.openSession(true);
}

点击打开 DefaultSqlSessionFactory 类查看对应的方法:

public SqlSession openSession(boolean autoCommit) {
    return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;

    DefaultSqlSession var8;
    try {
        // 获取环境配置信息
        Environment environment = this.configuration.getEnvironment();
        // 获取 environment 节点下的事务配置信息,默认情况下是 ManagedTransactionFactory;
    // 如果配置了 transactionManager 节点并且 type 为 JDBC,则返回 JdbcTransactionFactory 事务工厂
        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
        // 通过事务工厂来新建一个事务,设置数据源、事务等级、是否自动提交
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 生成一个执行器对象(事务包含在里面)
        Executor executor = this.configuration.newExecutor(tx, execType);
        // 新建一个 DefaultSqlSession
        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    } catch (Exception var12) {
        // 如果打开事务出错,则关闭事务
        this.closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
    } finally {
        ErrorContext.instance().reset();
    }

    return var8;
}

总结

时序图:
获取SqlSession
关系图:
类的关系

参考

知乎专栏 - Mybatis源码详解系列
稀土掘金 - SessionFactory机制原理
打 赏
  • 支付宝
  • 微信
Alipay
WeChatPay
评论区
暂无评论
avatar