一、前言

SqlSessionFactory 是创建 SqlSession 对象的工厂,单例,在应用程序中有且仅有一个实例对象。
SqlSession 是暴露给用户的调用数据库查询接口的 API,该对象 非线程安全。因此,每个线程应使用不同的 SqlSession 对象。当然,Mybatis 也提供内置的线程安全的类 org.apache.ibatis.session.SqlSessionManager (内部是使用 ThreadLocal 保证线程安全)。

二、SqlSessionFactory

2.1 API 及继承关系

2.1.1 API

SqlSessionFactory_interface.png
所定义的接口大多数是重载方法 openSession ,也就是获取 SqlSession 会话。
接口参数大致包括:

  • 是否自动提交。
  • 数据库连接对象 Connection
  • 执行器类型,分别有 SIMPLE(默认执行器类型)、REUSE、BATCH。
  • 事务隔离级别,分别有 NONE、READ_COMMITED、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE。

    2.1.2 继承关系

    SqlSessionFactory.png

  • DefaultSqlSessionFactory: Mybatis 默认实现。

  • SqlSessionManager
    • 内部持有 SqlSessionFactory 对象和本地线程变量 ThreadLocal<SqlSession>,即每个线程拥有同一个 SqlSession 对象,通过它构建的 SqlSession 对象是 线程安全 的。而 DefaultSqlSessionFactory 所构建的 SqlSession 对象是 非线程安全
    • SqlSessionManagement 同时实现 SqlSession 接口,拥有执行 Sql 查询能力。在用户调用执行时,通过动态代理从 ThreadLocal<SqlSession> 变量中获取对应线程的 SqlSession,并执行相应的方法。

简单来说,SqlSessionManager 是一个 SqlSession 管理器,通过它的 API 调用会保证所使用的 SqlSession 线程安全。

2.2 SqlSessionFactory 如何生产 SqlSession 对象?

SqlSessionFactory 获取 SqlSession 对象核心代码如下。获取步骤为:

  1. 从配置环境中获取环境对象(配置文件解析全局配置标签 <environment>)。
  2. 根据配置环境获取事务工厂。
  3. 利用反射生成事务对象(一般为 JdbcTransaction, 当与 Spring 框架整合,为 ManagedTransaction)。
  4. 根据事务以及执行类型获取执行器(默认执行器为 SIMPLE, 所以获得 SimpleExecutor)。
  5. 生成的对象统一用 DefaultSqlSession 进行包装并返回。

核心代码如下:
DefaultParameterHandler#openSessionFromDataSource.png
SqlSession_get.png

三、SqlSession

SqlSession 是 Mybatis 工作的主要 API 接口,通过这些接口你拥有执行命令、获取 mappers 以及管理事务的能力。

3.1 API

SqlSession_interfaces.png
该接口定义了与数据库相关的增、删、改、查等一系列接口,还包含了事务开启/关闭/回滚等操作。
还有一个非常重要的接口 getMapper(Class) 获取经过 JDK 动态代理 Mapper 接口的代理类对象。
该接口的默认实现类只有一个DefaultSqlSession
下面我们简单分析 SqlSession 在 Mybatis 执行 SQL 链路中是承担哪些职责。

3.2 SqlSession 承担的职责

通过源码分析,SqlSession 关于 SELECT|UPDATE|DELETE|INSERT 等API 其他做的内容大致相同,

  1. 从配置类中获取 MappedStatement 对象,该对象包含 Mapper 文件所定义的一切内容,比如 SQL 语句,参数对象、结果对象、类型处理器等等。
  2. 交给 Executor 执行器执行数据库查询。

selectOne(String, object, RowBounds) API 进行简要分析。

selectOne() API

这个接口不接收任何 SQL 参数,是最简单的 SQL 查询 API。
由于selectOne() 也是 selectList() 的其中一种情况,最终会调用下面这个方法:
DefaultSqlSession_selectlist.png
有意思的是,其他操作 DELETEINSERT 都是调用 UPDATE API 执行:
DefaultSqlSession_other_operations.png
至于 Excutor 到底如何执行 SQL ,后续会分析。

3.3 getMapper(Class)

我们可以对 getMapper(Class) API 做一个细致的分析,因为我们都知道在使用 Mybatis 框架进行编程时,需要同时编写 Mapper 接口类和与之相对应的 Mapper 映射文件,我们只需要通过 getMapper(Class) 就通过所定义的接口实现类并执行相应的方法,Mybatis 就能帮助我们查询数据库并返回相应的类型/集合等数据体。我们也知道,Mybatis 是通过 JDK 动态代理完成这一工作,那下面,我们就通过源码的方式过一遍基本流程。

两个阶段

  • Mybatis 初始化。解析 Mybatis 配置文件、Mapper 映射文件、Mapper接口。
    • XMLMapperBuilder#bindMapperForNamespace() 方法根据 Mapper 文件的 namespace 获取 Class 实例,通过 configuration.addMapper(Class) 方法注册到 MapperRegistry 对象中。此时并未进行动态代理。
  • getMapper(Class) 调用
    • 根据类型获取 MapperProxyFactory(里面包含对应 Class 引用) ,调用 MapperProxyFactory#newInstance 进行动态代理。

源码片段一
MapperRegistry#getMapper.png

  1. 从缓存中获取 MapperFactory 工厂类,该类用来创建 Mapper 接口代理对象。
  2. 通过工厂类创建 Mapper 接口的代理类。当我们获取到 Mapper 对象时,实际上是代理对象。

那对于代理对象而言,我们就应该着重看他的 InvocationHandler 方法,实现类是 org.apache.ibatis.binding.MapperProxy

MapperProxy

MapperProxy 实现 JDK java.lang.reflect.InvocationHandler 重要的是它的 invoke() 方法。
源码片段二
MapperProxy#invoke.png

MapperMethodInvoker

MapperMethodInvoker 接口代理是写在 MapperProxy 类里面的,它有两个实现类,继承关系如下:
MapperMethodInvoker.png
两者区别是: 如果当前方法是被 default 关键字标识,则创建 DefaultMethodInvoker 对象,否则,创建 PlainMethodInvoker
我们看一个 PlainMethodInvoker 都做了些什么?
PlainMethodInvoker.png
里面持有 MapperMethod 对象,由它代理完成 SQL 的执行。
MapperMethod#execute.png
看到了熟悉的 SELECT|DELETE 等方法,最终还是交给 SqlSession 对象完成 SQL 的执行。

四、总结

  • SqlSessionFactory 解析全局配置文件并存入 Connfiguration 对象中,这里面包含了 Mybatis 的一切。
  • SqlSession 是 Mybatis 暴露给用户调用增、删、改、查的 API 接口,它是 非线程 安全的,但是 SqlSessionManager 使用 ThreadLocal 变量给用户提供了一个线程安全的 SqlSession 管理器,并且实现了 SqlSession 接口,因为也有了查询的能力。
  • SqlSession 做的事情并不多,都是委托给执行器执行。
  • getMapper(Class) 是核心接口,当调用 Mapper 接口定义的方法时,Mybatis 框架通过动态代理底层执行连接获取、参数封装、数据库查询、结果集处理、类型转换等一系列流程。