源码分析-SqlSession 剖析

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 17 天,点击查看活动详情

本期课程我们继续进行源码剖析。通过本节剖析源码来知道 SQL 语句它的一个执行流程。

在进行源码剖析之前,我们先简单介绍一下 SqlSession,因为接下来我们就是要对这个对象进行一下源码剖析。

SqlSession

关于SqlSession的作用,官方文档是这样介绍的:

The primary Java interface for working with MyBatis.
Through this interface you can execute commands, get mappers and manage transactions.
复制代码

翻译为:SqlSession 是 MyBatis 的关键对象,通过这个接口可以操作命令,管理事务等。

首先我们来看 SqlSession 接口中定义的方法,如下图所示,SqlSession 接口定义了数据库操作的基本方法,其中select*() 方法、update() 方法、insert() 方法、delete() 方法以及 flushStatement() 方法是执行 SQL 语句的基础方法,commit() 方法、rollback() 方法与事务的提交/回滚相关,clearCache() 方法与缓存有关,close() 方法与会话相关,getConfiguration() 方法与配置有关,getMapper() 方法与动态代理,getConnection() 方法与数据库连接相关。

是一个面向用户的接口,SqlSession中定义了数据库操作方法。每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。

打开一个SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。

image.png

SqlSession 接口只是一个前台客服,真正发挥作用的是 Executor,对 SqlSession 方法的访问最终都会落到 Executor 的相应方法上去,Executor 如何具体实现,在后期课程在做介绍。

SqlSession 接口有两个实现类,分别是 DefaultSqlSession 和 SqlSessionManager,如下图所示:

image.png   其中 DefaultSqlSession 是 SqlSession 接口的默认实现类,SqlSessionManager 已经被弃用了,所以我们也不用多做介绍。

SqlSession 线程安全性

我们知道,SqlSession 是 MyBatis 中用于和数据库交互的一个顶层类,通常我们把它和ThreadLocal,也就是本地线程进行绑定,一次绘画就使用一个 SqlSession。

SqlSession 对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了 JDBC 连接,可以用 SqlSession 实例来直接执行已映射的 SQL 语句。

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

DefaultSqlSession

我们都知道 DefaultSqlSession 是 SqlSession 接口的默认实现类,在这个类中有两个重要属性,分别是 Configuration 和 Executor,同时也是 SqlSession 中最重要两个参数。

public class DefaultSqlSession implements SqlSession {
  private final Configuration configuration;
  private final Executor executor;
}
复制代码

其中,Configuration 我们初始化时封装好的这个 consideration 是相同的。

Executor 是一个执行器。为什么我们说 Executor 是 DefaultSqlSession 中非常重要的一个参数呢?因为在 SqlSession 在调用方法执行的过程中,它会把这个任务最终会委派给 Executor 这个执行器。

介绍完 SqlSession 接口以及实现类之后,介绍我们以 selectList() 方法为例,分析 SQL 语句具体执行。

SqlSession sqlSession = build.openSession();
List<Object> objects = sqlSession.selectList("namespace.id");
复制代码

openSession

前面,我们简单的介绍了 SqlSession。接着,我们继续源码分析。

首先,我们分析 openSession() 方法具体实现功能。openSession() 方法是如何生产 SqlSession 对象。

openSession() 方法是定义在 SqlSessionFactory 接口中,它的实现是在 DefaultSqlSessionFactory 类中。

在 DefaultSqlSessionFactory 类中,openSession() 方法去调用了 openSessionFromDataSource() 方法。

public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
复制代码

同时,传递了三个参数:

  • 第一个参数是 configuration.getDefaultExecutorType(),这个参数是从 configuration 这个对象中获取到当前默认执行器它的一个类型。在 getDefaultExecutorType() 方法中直接返回 defaultExecutorType。
public ExecutorType getDefaultExecutorType() {
  return defaultExecutorType;
}
复制代码

defaultExecutorType 类型就是一个 SIMPLE 类型。

protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
复制代码

所有当前我们的这里执行器 Executor 的类型就是 SIMPLE 类型。

  • 第二个参数是 level,指的是我们当前数据库事务的一个隔离级别,这里没有指定数据库事务的隔离级别,所有是 null 值。

  • 第三个参数是 autoCimmit,是指是否自动提交事务。我们当前给的这个参数的值 false,表示生产出来这个 sqlSession 的事务不自动提交。

紧接着,我们来分析 openSessionFromDataSource() 方法源码。

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();
  }
}
复制代码

接着分析 openSessionFromDataSource() 方法具体执行步骤:

  • 第一步:获得 Environment 对象,指的是运行环境对象。

  • 第二步:获得 Transaction 对象。指的是事务对象。

  • 第三步:通过 configuration.newExecutor() 方法创建类一个 Executor 对象,此时执行器对象就创建了。在使用 sqlSessionFactory.openSession 的过程中,Executor 也进行了实例化。

  • 第四步:返回 new DefaultSqlSession,这步生产当前 SqlSession 的实现类对象 DefaultSqlSessionFactory。

简单总结一下,在生产 SqlSession 过程中,实现了什么操作呢?生产了 SqlSession 或者 DefaultSqlSession 实例对象,同时它在生产这个 SqlSession 实例对象的过程中,还去设置了事务是不自动提交的,以及去完成了我们的 Executor 对象的一个创建。这个就是 openSession 完成功能。

有了 sqlSession 对象之后,通过调用 SqlSession 接口的方法,完成与数据库进行交互。这里我们以 selectList() 方法具体分析底层如何执行 SQL 语句。

在调用 selectList() 方法时,需要传递 statementId,这个 id 有 namespace+id 组成,同时,它的返回值是一个 List 集合。

selectList() 方法底层如何 SQL 语句呢?selectList() 方法定义在 SqlSession 接口中,它的具体实现是在 DefaultSqlSession 中。

DefaultSqlSession 类中,selectList() 方法有众多重载方法,在当前分析的这个例子中,我们只设置了 statementId,在调用重载方法的时候,将 parameter 设置为 null。

@Override
public <E> List<E> selectList(String statement) {
  return this.selectList(statement, null);
}
复制代码

在它的重载方法中,又去调用了另外一个重载方法,设置了三个参数,分别是 statement、parameter 以及 RowBounds.DEFAULT,RowBounds.DEFAULT 是一个分页对象。

@Override
public <E> List<E> selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
复制代码

再一次调用它的重载方法,此时设置四个参数,其实之前设置三个参数之外,又添加一个 Executor.NO_RESULT_HANDLER 参数,这个参数是一个执行器对象。

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
复制代码

它最执行的重载方式是下面这个方法。

private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
复制代码

在这个 selectList() 重载方法中,它具体执行步骤:

  • 第一步:通过 configuration.getMappedStatement() 方法,根据传入 statementId 从 configuration 对象的的 MappedStatement 集合中获取 MappedStatement 对象。

  • 第二步:执行具体查询操作,这一步是具体执行 SQL 语句操作。它真正执行的操作是交给了 Executor 执行器来完成。

到目前为止,selectList() 方法具体执行过程分析完了,简单总结一下,它具体完成功能。首先根据 statementId 来从 configuration 中 Map 集合中获取到了指定了 MappedStatement 对象,其次将真正要执行 SQL 语句的任务委派给 Executor 执行器。

猜你喜欢

转载自juejin.im/post/7109079991379296269