前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis源码阅读(二) --- 执行流程分析

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

作者头像
终有救赎
发布2023-12-22 14:20:02
1470
发布2023-12-22 14:20:02
举报
文章被收录于专栏:多线程多线程
一、概述

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

二、Mybatis核心类

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

  • SqlSessionFactory

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

代码语言:javascript
复制
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类中主要的方法:

代码语言:javascript
复制
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
图片.png
  • StatementHandler:负责处理JDBC的statement的交互,包括对statement设置参数、以及将JDBC返回的结果集转换为List等;
  • ParameterHandler:负责根据传递的参数值,对statement对象设置参数;
  • ResultSetHandler:负责解析JDBC返回的结果集;
  • TypeHandler:负责jdbcType与javaType之间的数据转换;

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

三、MyBatis执行流程
代码语言:javascript
复制
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()方法的源码:

代码语言:javascript
复制
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对象中,方便后续各个对象直接获取。

代码语言:javascript
复制
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()方法:

代码语言:javascript
复制
//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()方法,如下所示:

代码语言:javascript
复制
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

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

图片.png
图片.png

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

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

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

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

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

代码语言:javascript
复制
//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
图片.png
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、概述
  • 二、Mybatis核心类
  • 三、MyBatis执行流程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档