MyBatis源码阅读(二) --- 执行流程分析

Java
229
0
0
2024-02-21
标签   MyBatis
一、概述

前面一篇文章我们已经搭建好了Mybatis的源码调试环境,那么今天我们先来看看MyBatis整体的执行流程是怎样的,先对整体有个了解,后面我们再针对各个细节进行分析。在分析执行流程之前,我们先对Mybatis中几个核心类做个简单的介绍。

二、Mybatis核心类

Mybatis核心类主要有下面几个:

  • SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或通过Java的方式构建出 SqlSessionFactory 的实例。SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。一个SqlSessionFactory对应配置文件中的一个环境(environment),如果你要使用多个数据库就配置多个环境分别对应一个 SqlSessionFactory。SqlSessionFactory类的主要方法:

public interface SqlSessionFactory {
 
  SqlSession openSession();
 
  SqlSession openSession(boolean autoCommit);
 
  SqlSession openSession(Connection connection);
 
  SqlSession openSession(TransactionIsolationLevel level);
 
  SqlSession openSession(ExecutorType execType);
 
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
 
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
 
  SqlSession openSession(ExecutorType execType, Connection connection);
 
  Configuration getConfiguration();
 
}

SqlSessionFactory其实是一个工厂,是为了创建SqlSession对象。SqlSession是MyBatis面向数据库的高级接口,其提供了执行查询sql,更新sql,提交事务,回滚事务,获取映射代理类等等方法。

  • SqlSession

SqlSession作为Mybatis的顶层API接口,作为会话访问,完成增删改查操作。它是一个接口,它有2个实现类,分别是DefaultSqlSession(默认使用)和SqlSessionManager。SqlSession通过内部存放的执行器(Executor)来对数据进行CRUD操作。此外SqlSession不是线程安全的,所以每一次操作完数据库后都要调用close对其进行关闭,官方建议通过try-finally来保证总是关闭SqlSession。

我们来看一下SqlSession类中主要的方法:

public interface SqlSession extends Closeable {
 
  /**
  * 查询一个结果对象
  **/ 
  <T> T selectOne(String statement, Object parameter);
  
   /**
  * 查询一个结果集合
  **/ 
  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  
   /**
  * 查询一个map
  **/ 
  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
 
   /**
  * 查询游标
  **/ 
  <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
 
  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  
     /**
  * 插入
  **/ 
  int insert(String statement, Object parameter);
 
    /**
  * 修改
  **/ 
  int update(String statement, Object parameter);
 
  /**
  * 删除
  **/
  int delete(String statement, Object parameter);
 
   /**
  * 提交事物
  **/
  void commit(boolean force);
  
   /**
  * 回滚事物
  **/
  void rollback(boolean force);
 
  List<BatchResult> flushStatements();
 
  void close();
 
  void clearCache();
 
  Configuration getConfiguration();
 
   /**
  * 获取映射代理类
  **/
  <T> T getMapper(Class<T> type);
 
   /**
  * 获取数据库连接
  **/
  Connection getConnection();
}
  • Executor

Mybatis执行器,负责SQL动态语句的生成和查询缓存的维护。它有两个实现类,其中BaseExecutor有三个继承类分别是:

  1. BatchExecutor批处理型执行器(批量操作);
  2. ReuseExecutor重用型执行器(重用预处理语句prepared statement,跟Simple的唯一区别就是内部缓存statement);
  3. SimpleExecutor简单型执行器(默认是SimpleExecutor,每次都会创建新的statement);
  • MappedStatement

MappedStatement用来封装我们mapper.xml映射文件中的信息,包括sql语句,输入参数,输出参数等。一个SQL节点对应一个MappedStatement对象。

  • Handler

处理器,Mybatis中有3种类型的Hanlder,分别为StatementHandler、ParameterHandler、ResultSetHandler。ParameterHandler主要解析参数,为Statement设置参数,ResultSetHandler主要是负责把ResultSet转换成Java对象。其实执行器Executor是调用StatementHandler来执行数据库操作。

图片.png

  • StatementHandler:负责处理JDBC的statement的交互,包括对statement设置参数、以及将JDBC返回的结果集转换为List等;
  • ParameterHandler:负责根据传递的参数值,对statement对象设置参数;
  • ResultSetHandler:负责解析JDBC返回的结果集;
  • TypeHandler:负责jdbcType与javaType之间的数据转换;

接下来我们阅读源码的时候,也基本上是根据这几个关键的核心类为中心,总结一下各个组件分别发挥了什么作用。

三、MyBatis执行流程
public static void main(String[] args) {
    //1、读取配置文件
    String resource = "mybatis-config.xml";
    InputStream inputStream;
    SqlSession sqlSession = null;
    try {
        inputStream = Resources.getResourceAsStream(resource);
        //2、初始化mybatis,创建SqlSessionFactory类实例
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        System.out.println(sqlSessionFactory);
        //3、创建Session实例
        sqlSession = sqlSessionFactory.openSession();
        //4、获取Mapper接口
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //5、执行SQL操作
        User user = userMapper.getById(1L);
        System.out.println(user);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        //6、关闭sqlSession会话
        if (null != sqlSession) {
            sqlSession.close();
        }
    }
}

第一步:Resources.getResourceAsStream(resource)

首先我们在mybatis-config.xml中环境,映射文件路径、插件,反射工厂,类型处理器等等其它内容,所以第一步肯定也是去读取这个配置。通过Resources加载配置好的mybatis-config.xml配置文件。Resources是ibatis.io包下面的类,也就是一个io流,用于读写文件,通过getResourceAsStream把xml文件加载进来,把配置文件解析为一个流。解析生成的文件流对象用于构建SqlSessionFactory。

第二步:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

这里采用了构建者模式,通过上一步解析生成的配置文件流对象,调用SqlSessionFactoryBuilder的build()创建SqlSessionFactory。

我们看一下build()方法的源码:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //build()方法创建DefaultSessionFactory对象
    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.
    }
  }
}

XMLConfigBuilder 是用来解析XML文件的一个构建者,通过他的parse()方法解析mybatis配置文件。

我们看一下parse()方法的源码,parse方法返回的是一个configuration对象,我们之前配置的插件,对象工厂,反射工厂,映射文件,类型解析器等等解析完成后都存储在Configuration对象中,方便后续各个对象直接获取。

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析configuration标签下的所有配置
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

可以看到,里面具体调用了parseConfiguration()方法对configuration标签下的子节点的所有配置进行解析。继续看一下parseConfiguration()方法:

//org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
  try {
    //先解析properties. 这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。
    propertiesElement(root.evalNode("properties"));
    //解析全局配置settings相关配置
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    //typeAliases别名配置解析, 别名配置有两种方式: a.指定单个实体; b.指定包路径
    //类型别名是为Java类型设置一个短的名字, 用来减少类完全限定名的冗余
    typeAliasesElement(root.evalNode("typeAliases"));
    //插件解析
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // environments里面其实就包含我们的数据库连接信息等的配置,重点,下面详细介绍.
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    //类型转换器相关配置解析,用于数据库类型和Java数据类型的转换
    typeHandlerElement(root.evalNode("typeHandlers"));
    //mapper接口配置解析,重点,下面详细介绍
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

如上可以看到,parseConfiguration()方法其实就是对我们mybatis-config.xml中具体的标签内容属性的解析。如environment、mapper、setting等标签的解析,解析之后的所有配置都将会保存在了全局配置对象configuration中。

parseConfiguration()方法执行完后,意味着parse()方法也执行完成,我们发现parse()最后返回了一个configuration对象,它是用来存放mybatis核心配置文件解析完成后的结果。继续看源码,我们看到这个configuration配置对象返回给了SqlSessionFactory的build()方法,如下所示:

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

可以看到, build()方法返回了一个DefaultSqlSessionFactory对象,这是SqlSessionFactory的实现类。如下图,SqlSessionFactory有两个实现类,其中一个就是DefaultSqlSessionFactory,另外一个是SqlSessionManager,默认返回的是DefaultSqlSessionFactory。

图片.png

到这里,我们的第二步就算完成了。

第三步:SqlSession sqlSession = sqlSessionFactory.openSession();

这一步主要的目的是生成SqlSession会话,通过sqlSessionFactory的openSession()方法开启一个会话。注意,SqlSession不是线程安全的,所以每次使用完记得都关闭它。SqlSession是MyBatis面向数据库的高级接口,其提供了执行查询sql,更新sql,提交事物,回滚事物,获取映射代理类等方法。

下面我们来看openSession()的源码:

获取sqlSession有两种方式,一种是从数据源中获取的,还有一种是从连接中获取,默认是从数据源中进行获取,如下:

//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
public SqlSession openSession() {
    //默认是从数据库连接中获取一个SqlSession会话
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
 
//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
    final Environment environment = configuration.getEnvironment();
    //事务工厂,这里是JbdcTransactionFactory工厂类
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    //通过事务工厂创建JbdcTransaction事务
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    //创建CachingExecutor执行器
    final Executor executor = configuration.newExecutor(tx, execType);
    //创建DefaultSqlSession会话,属性主要包括Configuration、Executor对象
    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();
  }
}

可以看到,上面的方法返回了一个DefaultSqlSession对象。看看它里面有些什么。

  • 【a】environment

环境对象,这是在mybatis-config.xml中配置的,主要用来生成TransactionFactory,而TransactionFactory是用来生成Transaction事务的。

  • 【b】Transaction

事务对象,我们都知道sql执行时涉及到事务,需要提交或回滚什么的。创建Transaction事务对象,需要传入我们在mybatis-config.xml中配置的数据源信息(从environment获取,因为之前解析XML的时候保存进去了),通过这些参数,transactionFactory就可以生成Transaction。

  • 【c】executor

执行器,非常重要,它是一个接口,是Mybatis的核心执行器,相当于jdbc中的statement,发送sql语句并执行。executor的继承图如下所示,默认使用的是SimpleExecutor简单类型的执行器。

图片.png