1、Mybatis 架构与核心API
不出意外的话,在后续源码剖析相关文章中,我们会对 Mybatis 的源码进行一次大扫荡,一起挖掘每一处值得大家深入理解/记忆的知识点。而在本文中,我们主要先把 Mybatis 的架构/层次铺开,俯视 Mybatis 架构的设计全貌,再把几个硬核的 API 详细消化。
整体顺序脉络,希望让你有所期待 ~
我们先简单揭开 Mybatis 神秘的源码包,
瞅瞅 Ta 大致目录结构 :
看,Mybatis 的源代码包整齐划一排在 org.apache.ibatis 目录下,基本设计用途我简单梳理成上面这张图,方便大家直观理解,当然只看源码包目录结构,难免会显得枯燥无物,所以我们再看一下,其实 Mybatis 的源码包功能上可以是这么划分:
上图让我们对 Mybatis 的架构有了抽象的理解。
然而,实际上具体的职能分工,核心 API 的场景应用,到底会是怎样一套流程呈现呢?
看下面这幅功能架构设计,或许你能更好的理解。
根据 Mybatis 功能架构我们划分成三层:
- 接口层:该层提供一系列接口让用户直接参与使用,包含信息配置与实际数据操作调用。配置方式包括:基于 XML 配置方式、基于 Java API 配置方式两种方式,用户也可以通过接口层 API 对数据库发起增删改查等操作的请求, 本层接口会把接收到的调用请求交给数据处理层的构件去处理。
- 数据处理层:该层是 Mybatis 的核心层,负责数据处理,主要包括SQL 参数映射解析、SQL 语句的实际执行、执行结果集的映射处理等。
- 框架支撑层:该层属于 Mybatis 的后勤保障层,包括数据库连接管理、事务把控管理、配置加载与缓存处理、日志处理、异常处理等,提供基础支撑能力,保障上层的数据处理。
我们知道,Mybatis 框架让用户只需要提供配置信息,并且专注于 SQL 的编写即可,对于连接管理数据库/事务,或实际的 SQL 参数映射/语句执行/结果集映射等操作,作为用户都并不需要操心和参与。
但是,好奇的我们其实想知道,Mybatis 核心部分的数据处理在整体流程中,是如何支撑用户请求?同时各个构件之间交互,又是怎样流转呢?
很巧,我这里有一张图,介绍了整体流程:
根据以上框架流程图进行讲解:
- 创建配置并调用API :这一环节发生在应用程序端,是开发人员在实际应用程序中进行的两步操作,第一步创建核心配置文件 Configuration.xml 和映射文件 mapper.xml (通过注解方式也可创建以上两种配置),准备好基础配置和 SQL 语句之后;第二步就是直接调用 Mybatis 框架中的数据库操作接口。
- 加载配置并初始化 :Mybatis 框架会根据应用程序端提供的核心配置文件与 SQL 映射文件的内容,使用资源辅助类 Resources 把配置文件读取成输入流,然后通过对应的解析器解析并封装到 Configuration 对象和 MappedStatement 对象,最终把对象存储在内存之中。
- 创建会话并接收请求 :在 Mybatis 框架加载配置并初始化配置对象之后,会话工厂构建器 SqlSessionFactoryBuilder 同时创建会话工厂 SqlSessionFactory,会话工厂会根据应用程序端的请求,创建会话 SqlSession,以便应用程序端进行数据库交互。
- 处理请求 :SqlSession 接收到请求之后,实际上并没有进行处理,而是把请求转发给执行器 Executor,执行器再分派到语句处理器 StatementHandler ,语句处理器会结合参数处理器 ParameterHandler ,进行数据库操作(底层封装了 JDBC Statement 操作)。
- 返回处理结果 :每个语句处理器 StatementHandler 处理完成数据库操作之后,会协同 ResultSetHandler 以及类型处理器 TypeHandler ,对底层 JDBC 返回的结果集进行映射封装,最终返回封装对象。
针对以上总体框架流程涉及到的这些硬核 API,下面我们逐个展开介绍,但不会详细剖析源码与原理,包括构建细节,因为这些我们在后续的源码剖析章节中都会详细分析。
2、Configuration – 全局配置对象
对于 Mybatis 的全局配置对象 Configuration,我相信无论是初学者还是资深玩家,都不会陌生。整个 Mybatis 的宇宙,都围绕着 Configuration 转。Configuration 对象的结构和 config.xml 配置文件的内容几乎相同,涵盖了properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理器),objectFactory (对象工厂),mappers (映射器)等等,之前我们有专门的一篇文章详细进行介绍,感兴趣的朋友往上翻到目录列表,找到 《Mybatis系列全解(四):全网最全!Mybatis配置文件XML全貌详解》 一文详细品味一番吧。
配置对象 Configuration 通过解析器 XMLConfigBuilder 进行解析,把全局配置文件 Config.xml 与 映射器配置文件 Mapper.xml 中的配置信息全部构建成完整的 Configuration 对象,后续我们源码分析时详细剖析整个过程。
3、Resources – 资源辅助类
我们知道,像 Configuration 和 Mapper 的配置信息存放在 XML 文件中,Mybatis 框架在构建配置对象时,必须先把 XML 文件信息加载成流,再做后续的解析封装,而 Resources 作为资源的辅助类,恰恰干的就是这个活,无论是通过加载本地资源或是加载远程资源,最终都会通过 类加载器 访问资源文件并输出文件流。
- //加载核心配置文件
- InputStream resourceAsStream =
- Resources.getResourceAsStream("Config.xml");
Resources 实实在在提供了一系列方法分分钟解决你的文件读取加载问题:
4、SqlSessionFactoryBuilder – 会话工厂构建器
我们一撞见 xxxBuilder ,就大致能知道它是某类对象的构建器,这里 SqlSessionFactoryBuilder 也是一样,它是 Mybatis 中的一个会话工厂构建器,在资源辅助类 Resources 读取到文件流信息之后,它负责解析文件流信息并构建会话工厂 SqlSessionFactory。(解析的配置文件包含:全局配置 Configuration 与映射器 Mapper)
在程序应用端,我们一般使用 SqlSessionFactoryBuilder 直接构建会话工厂:
- // 获得sqlSession工厂对象
- SqlSessionFactory sqlSessionFactory =
- new SqlSessionFactoryBuilder().build(resourceAsStream);
当然,如果你集成了 Spring 框架的项目,则不需要自己手工去构建会话工厂,直接在 Spring 配置文件中指定即可,例如指定一个 bean 对象,id 是 sqlSessionFactory,而 class 类指定为 org.mybatis.spring.SqlSessionFactoryBean 。
SqlSessionFactoryBuilder 内部通过解析器 XMLConfigBuilder 解析了文件流,同时封装成为配置对象 Configuration ,再把 Configuration 对象进行传递并构建实例。
- public SqlSessionFactory build(
- InputStream inputStream,
- String environment,
- Properties properties) {
- // 配置解析器解析
- XMLConfigBuilder parser =
- new XMLConfigBuilder(
- inputStream,environment, properties);
- // 最终实例会话工厂
- return build(parser.parse());
- }
最终实例会话工厂,其实 Mybatis 默认实现是 new 了一个DefaultSqlSessionFactory 实例。
- // 最终实例会话工厂
- public SqlSessionFactory build(Configuration config) {
- return new DefaultSqlSessionFactory(config);
- }
会话工厂构建器 SqlSessionFactoryBuilder 应用了构建者模式,主要目的就是为了构建 SqlSessionFactory 对象,以便后续生产 SqlSession 对象,这个构造器基本上算是 Mybatis 框架的入口构建器,它提供了一系列多态方法 build(),支持用户使用 XML 配置文件或 Java API (Properties)来构建会话工厂 SqlSessionFactory 实例。
SqlSessionFactoryBuilder 的一生只为成就 SqlSessionFactory,当 SqlSessionFactory 一经实例,SqlSessionFactoryBuilder 使命完成,便可消亡,便可被丢弃。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是 方法作用域 (也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
SqlSessionFactoryBuilder 中灵活构建会话工厂的一系列接口:
5、SqlSessionFactory – 会话工厂
会话工厂 SqlSessionFactory 是一个接口,作用是生产数据库会话对象 SqlSession ,有两个实现类:
- **DefaultSqlSessionFactory **(默认实现)
- **SqlSessionManager **(仅多实现了一个 Sqlsession 接口,已弃用)
在介绍会话工厂构建器 SqlSessionFactoryBuilder 的时候,我们了解到构建器默认创建了 DefaultSqlSessionFactory 实例,并且会话工厂本身会绑定一个重要的属性 Configuration 对象,在生产会话时,最终也会把 Configuration 配置对象传递并设置到会话 SqlSession 上。
会话工厂可以简单创建 SqlSession 实例:
- // 创建 SqlSession 实例
- SqlSession session = sqlSessionFactory.openSession();
会话工厂创建 SqlSession 时,会绑定数据源、事务处理、执行器等等,默认会话工厂实现类 DefaultSqlSessionFactory 在创建会话对象时,最终都会调用 openSessionFromDataSource 方法 ,即是如此实现:
- // 每一个 openSession 最终都会调用此处
- private SqlSession openSessionFromDataSource(
- ExecutorType execType,
- TransactionIsolationLevel level,
- boolean autoCommit) {
- // 环境配置
- final Environment environment =
- configuration.getEnvironment();
- // 事务工厂
- final TransactionFactory transactionFactory =
- getTransactionFactoryFromEnvironment(environment);
- // 事务
- Transaction tx =
- transactionFactory.newTransaction(
- environment.getDataSource(),
- level,
- autoCommit);
- // 执行器
- final Executor executor =
- configuration.newExecutor(tx, execType);
- // 最终生成会话
- return new DefaultSqlSession(
- configuration, executor, autoCommit);
- }
另外,会话工厂其实提供了一系列接口来灵活生产会话 SqlSession,你可以指定:
- 事务处理 :你希望在 session 作用域中使用/开启事务作用域(也就是不自动提交事务),还是使用自动提交(auto-commit),sqlSession 默认不提交事务,对于增删改操作时需要手动提交事务。
- 数据库连接 :你希望 MyBatis 帮你从已配置的数据源获取连接,还是使用自己提供的连接,可以动态创建数据源对象 Connection。
- 执行器类型 :你希望指定某类执行器来创建/执行/预处理语句,可以有普通执行器(SimpleExecutor),或复用执行器(ReuserExecutor)、还是批量执行器(BatchExecutor)等,下面介绍执行器时会详细说明。
- 事务隔离级别支持 :支持 JDBC 的五个隔离级别(NONE、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE),对于事务相关的内容,我们后续 《spring 系列全解》 会详细讲,这里简单说明一下就是事务隔离级别主要为了解决例如脏读、不可重复读、幻读等问题,使用不同的事务隔离级别势必会导致不同的数据库执行效率,因此我们再不同的系统/功能中,对隔离级别有不同的需求。
SqlSessionFactory一旦被创建就应该在 应用的运行期间 一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是 应用作用域 。 最简单的就是使用单例模式或者静态单例模式。
请记住,创建 SqlSessionFactory ,一次就好!
每个数据库对应一个 SqlSessionFactory 实例。SqlSessionFactory 一旦被创建, 它的生命周期应该与应用的生命周期相同 。所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个;而如果是三个数据库,就需要三个实例,依此类推。
6、SqlSession – 会话
SqlSession 是一个接口,有两个实现类:
- DefaultSqlSession (默认实现)
- SqlSessionManager (已弃用)
简单来说,通过会话工厂构建出 SqlSession 实例之后,我们就可以进行增删改查了,默认实例 DefaultSqlSession 提供了如此多的方法供用户使用,有超过30个:
sqlSession 的方法除了 CURD,还提供了事务的控制例如提交/关闭/回滚等、提供了配置对象的获取例如 getConfiguration()、提供了批量语句的执行更新例如 flushStatements()、提供了缓存清除例如 clearCache() 、提供了映射器的使用 getMapper() 等等。
对于客户端应用层面来说,熟悉 sqlSession 的 API 基本就可以任意操作数据库了,不过我们希望想进一步了解 sqlSession 内部是如何执行 sql 呢?其实 sqlSession 是 Mybatis 中用于和数据库交互的 顶层类 ,通常将它与本地线程 ThreadLocal 绑定,一个会话使用一个 SqlSession,并且在使用完毕之后进行 关闭 。
之所以称 SqlSession 为数据交互的 顶层类 ,是它其实没有完成实质的数据库操作。根据之前的架构设计流程我们已经清晰的知道,SqlSession 对数据库的操作都会转发给具体的执行器 Executor 来完成 ;当然执行器也是甩手掌柜,执行器 Executor 会再分派给语句处理器 StatementHandler ,语句处理器会结合参数处理器 ParameterHandler ,共同完成最终的数据库执行处理操作(底层还是封装了 JDBC Statement 操作)。并在每个语句处理器 StatementHandler 处理完成数据库操作之后, 通过结果结处理器 ResultSetHandler 以及类型处理器 TypeHandler ,对底层 JDBC 返回的结果集进行映射封装,最终才返回预期的封装对象。
关注以下图示 sqlSession 红色高亮位置,详细描述了会话的实际执行路径:
SqlSession 可以理解为一次数据库会话,一次会话当中既可以执行一次 sql ,也允许你批量执行多次,但是一旦会话关闭之后想要再执行 sql,那就必须重新创建会话。
每个线程都应该有它自己的 SqlSession 实例,SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是 请求(request)或方法(method) 作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。