MyBatis源码阅读——通过debug解析MyBatis运行流程

前言

最近在阅读MyBatis框架的源码。发现它其实是一个非常值得阅读的框架。它灵活得运用了常见的设计模式去设计。值得我们去学习。我还是比较喜欢以debug阅读MyBatis的源码。下面,就一起来看看吧。
首先,我们先写一个demo,以供调试使用

public class Demo1SessionFactory {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis/conf/mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //从 XML 中构建 SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlog(1L);
            System.out.println(blog);
            blog = mapper.selectBlog(1L);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }
}

从demo分析上层流程

在demo中,我们先从最上层分析一下流程。这里写图片描述
我们首先是创建SqlSessionFactory,这其中需要使用配置信息。然后从SqlSessionFactory获取SqlSession,再从SqlSession中获取Mapper。这是我们最上层的流程。那这这几步操作中,内部操作了什么呢?我们需要debug去分析。在分析之前,我们需要先了解一下MyBatis的架构和使用的设计模式,这样能帮助我们更轻松的去了解它的源码。

作用域(Scope)和生命周期

  • SqlSessionFactory
    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

  • SqlSession
    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:

其实我感觉这个跟Servlet中的生命周期类似,SqlSessionFactory当做上下文(Application),SqlSession当做一个会话(session),这样就好理解了。一个SqlSession就是与数据库进行一次交互。

从作用域和生命周期的分析,我们可以分析出,它的核心是SqlSession。
查看org.apache.ibatis.session.defaults.DefaultSqlSessionFactory源码,查看SqlSession的获取实现过程

  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);
      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();
    }
  }

再看SqlSession类的结构:
SqlSession 主要由Configuration和Executor构成,其中有增删改查的各种方法。
这里写图片描述
由此我们可以推理出我们用MyBatis进行操作的主要操作入口都在这里。那么,接下来就涉及到我们是如何获取到具体的Mapper

具体Mapper的获取过程

查看 org.apache.ibatis.session.SqlSession ->getMapper() 找到实现

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}

继续深入,找到org.apache.ibatis.binding.MapperRegistry ->getMapper()

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

可以分析出这是通过动态代理获取的,而它在获取的时候需要两个参数Class type, SqlSession sqlSession。SqlSession是作为被代理的对象。type作为key去Map中拿MapperProxyFactory来产生代理类需要的参数。

Mapper的操作流程

现在,我们已经知道具体的Mapper的来历了,那么就可以去了解一下它是如何使用的。因为它是SqlSession进行了动态代理类,所以,我们debug SqlSession,进行一次查询操作。就如下图所示进入调试。
这里写图片描述
debug下去,你会看到,底层是通过Executor执行的
这里写图片描述
然后继续深入,可以看到sql的组装过程:
这里写图片描述

Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
接下来就是类似传统JDBC的操作了
这里写图片描述

MyBatis核心构件

名称 作用
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条select
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
Configuration MyBatis所有的配置信息都维持在Configuration对象之中

猜你喜欢

转载自blog.csdn.net/qq_18860653/article/details/80605690