一、前言

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
有意思的是,其他操作 DELETE 、INSERT 都是调用 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 框架通过动态代理底层执行连接获取、参数封装、数据库查询、结果集处理、类型转换等一系列流程。