一、前言
SqlSessionFactory
是创建 SqlSession
对象的工厂,单例
,在应用程序中有且仅有一个实例对象。SqlSession
是暴露给用户的调用数据库查询接口的 API,该对象 非线程安全
。因此,每个线程应使用不同的 SqlSession
对象。当然,Mybatis 也提供内置的线程安全的类 org.apache.ibatis.session.SqlSessionManager
(内部是使用 ThreadLocal
保证线程安全)。
二、SqlSessionFactory
2.1 API 及继承关系
2.1.1 API
所定义的接口大多数是重载方法 openSession
,也就是获取 SqlSession
会话。
接口参数大致包括:
- 是否自动提交。
- 数据库连接对象
Connection
。 - 执行器类型,分别有 SIMPLE(默认执行器类型)、REUSE、BATCH。
事务隔离级别,分别有 NONE、READ_COMMITED、READ_UNCOMMITTED、REPEATABLE_READ、SERIALIZABLE。
2.1.2 继承关系
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
对象核心代码如下。获取步骤为:
- 从配置环境中获取环境对象(配置文件解析全局配置标签
<environment>
)。 - 根据配置环境获取事务工厂。
- 利用反射生成事务对象(一般为
JdbcTransaction
, 当与 Spring 框架整合,为ManagedTransaction
)。 - 根据事务以及执行类型获取执行器(默认执行器为 SIMPLE, 所以获得 SimpleExecutor)。
- 生成的对象统一用 DefaultSqlSession 进行包装并返回。
三、SqlSession
SqlSession
是 Mybatis 工作的主要 API 接口,通过这些接口你拥有执行命令、获取 mappers 以及管理事务的能力。
3.1 API
该接口定义了与数据库相关的增、删、改、查等一系列接口,还包含了事务开启/关闭/回滚等操作。
还有一个非常重要的接口 getMapper(Class)
获取经过 JDK 动态代理 Mapper 接口的代理类对象。
该接口的默认实现类只有一个DefaultSqlSession
。
下面我们简单分析 SqlSession 在 Mybatis 执行 SQL 链路中是承担哪些职责。
3.2 SqlSession 承担的职责
通过源码分析,SqlSession
关于 SELECT|UPDATE|DELETE|INSERT 等API 其他做的内容大致相同,
- 从配置类中获取
MappedStatement
对象,该对象包含 Mapper 文件所定义的一切内容,比如 SQL 语句,参数对象、结果对象、类型处理器等等。 - 交给
Executor
执行器执行数据库查询。
以 selectOne(String, object, RowBounds)
API 进行简要分析。
selectOne() API
这个接口不接收任何 SQL 参数,是最简单的 SQL 查询 API。
由于selectOne()
也是 selectList()
的其中一种情况,最终会调用下面这个方法:
有意思的是,其他操作 DELETE
、INSERT
都是调用 UPDATE
API 执行:
至于 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
进行动态代理。
- 根据类型获取
源码片段一
- 从缓存中获取 MapperFactory 工厂类,该类用来创建 Mapper 接口代理对象。
- 通过工厂类创建 Mapper 接口的代理类。当我们获取到 Mapper 对象时,实际上是代理对象。
那对于代理对象而言,我们就应该着重看他的 InvocationHandler
方法,实现类是 org.apache.ibatis.binding.MapperProxy
。
MapperProxy
MapperProxy
实现 JDK java.lang.reflect.InvocationHandler
重要的是它的 invoke()
方法。
源码片段二
MapperMethodInvoker
MapperMethodInvoker 接口代理是写在 MapperProxy 类里面的,它有两个实现类,继承关系如下:
两者区别是: 如果当前方法是被 default
关键字标识,则创建 DefaultMethodInvoker
对象,否则,创建 PlainMethodInvoker
。
我们看一个 PlainMethodInvoker
都做了些什么?
里面持有 MapperMethod
对象,由它代理完成 SQL 的执行。
看到了熟悉的 SELECT|DELETE 等方法,最终还是交给 SqlSession
对象完成 SQL 的执行。
四、总结
SqlSessionFactory
解析全局配置文件并存入Connfiguration
对象中,这里面包含了 Mybatis 的一切。SqlSession
是 Mybatis 暴露给用户调用增、删、改、查的 API 接口,它是非线程
安全的,但是SqlSessionManager
使用ThreadLocal
变量给用户提供了一个线程安全的SqlSession
管理器,并且实现了SqlSession
接口,因为也有了查询的能力。SqlSession
做的事情并不多,都是委托给执行器执行。getMapper(Class)
是核心接口,当调用 Mapper 接口定义的方法时,Mybatis 框架通过动态代理底层执行连接获取、参数封装、数据库查询、结果集处理、类型转换等一系列流程。