MyBatis 是一个普遍应用并且十分优秀的持久层框架; 本文将简单介绍 MyBatis 的使用, 同时也将分析其在代码层面是如何实现的; 本文的演示环境如下:
- JDK1.8
- MySQL 8.0.15
- MyBatis 3.4.6
MyBatis 的使用
构建 SqlSessionFactory
每个基于 MyBatis 的应用都是以 SqlSessionFactory 为核心的, SqlSessionFactory 可以通过 SqlSessionFactoryBuilder 来构建; 常用的有两种方式, 一种是通过 xml 配置构建, 而另一种则是通过 Configuration 实例对象来构建;
通过 xml 构建:
xml 配置文件内容如下:
通过 Configuration 实例对象来构建:
获取到 SqlSessionFactory 以后, 我们便可以获取到 SqlSession, 最后便可以执行已经映射的 SQL 语句了; 具体代码如下:
执行 SQL 语句
执行 SQL 语句的方法也并非只用一种, 亦可以直接通过 SqlSession 来执行
通过 SqlSession 执行
通过获取 Mapper 来执行
随后会在下文针对映射语句, 执行语句进行较为详细的介绍.
映射的 SQL 语句
当然, 映射的 SQL 语句也是不一定必须通过 xml 文件来完成的, 也是可以通过注解来实现的
对于简单的 SQL 语句, 通过注解的方式, 你会觉得十分的得心应手; 然而实现复杂的 SQL 则会有些混乱和力不从心; 使用者可以根据自己的需求来使用, 而不用单单被局限于其中的某一种形式.
作用域以及生命周期
依赖注入框架会创建线程安全的 SqlSession 和 Mapper 并将它们注入到你的 bean 中, 因此你可以直接忽略它们的生命周期; 如何通过依赖注入框架来使用 MyBatis, 后面我们将会介绍; 你也可以参看 MyBatis-Spring.
SqlSessionFactoryBuilder
这个类可以被实例化, 创建以及销毁, 一旦在其创建 SqlSessionFactory, 其实就不需要它了; 所以它最好的作用域就是方法作用域 (局部方法), 当然你可以通过它创建多个 SqlSessionFactory 实例, 当然最好不要让 SqlSessionFactoryBuilder 一直存在, 以保证 xml 资源被用于更重要的事情.
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用运行期间一直存在; 它不应该被频繁的销毁创建, 所以它的作用域应该是应用作用域; 最好通过单例模式来使用.
SqlSession
每个线程都应该有自己的 SqlSession,SqlSession 不是线程安全的, 所以它不能被线程共享, 所以 SqlSession 不能被置于静态类, 或者一个类的实例变量; 所以它的作用域最好是请求或者方法. 例如在 HTTP 请求中, 应该每次收到一个请求, 便打开一个 SqlSession, 响应之后, 立即关闭 SqlSession.
Mapper 实例
通过使用的 Demo, 我们也知道 Mapper 实例是通过 SqlSession 获得的, 所以它的作用域也是方法作用域.
以上 MyBatis 使用指南, 主要参考 MyBatis 官方文档
源码解读
了解以上知识, 让我们对 MyBatis 有了进一步的了解; 便于我们捕捉源码的阅读方向; 我们知道 SqlSessionFactory 是通过 SqlSessionFactoryBuilder 来构建的, 接下来我们首先来看看它;
SqlSessionFactoryBuilder
我们看到 SqlSessionFactoryBuilder 中有这各式各样的 build 方法; 最终都调用到如下方法:
以上代码也印证了我们主要介绍的两种方法来构建 SqlSessionFactory, 最终会返回一个 DefaultSqlSessionFactory 实例.
SqlSessionFactory
我们可以看到 SqlSessionFactory 主要提供了开启 SqlSession 以及获取 Configuration 的方法; 知道了接口作用, 我们再来看看默认的实现类 DefaultSqlSessionFactory. 获取 SqlSession 最终都交给了两个私有的方法: openSessionFromDataSource,openSessionFromConnection; 顾名思义分别是通过数据源来获取, 通过连接来获取; 两个方法大同小异, 我们来详细看看其中一个.
以上便是创建 SqlSeesion 的过程, 利用 public DefaultSqlSession(Configuration configuration, Executor executor) 此构造方法来获取 SqlSession 实例.
SqlSession
我们发现 SqlSession 提供了所有对数据库的操作, 各式各样的增删改查, 以及获取映射 Mapper 的方法; 接下来我们仔细研读一下默认的实现类 DefaultSqlSession
我们发现在 DefaultSqlSession 实现类中 SqlSeesion 封装的对数据库的操作最终都是有 Executor 来执行的; 相当于 SqlSeesion 提供对数据库相应的操作, 而具体的职责是有 Executor 来完成的.
Executor
下图是 Executor 的主要功能
下图是 Executor 的继承实现关系
Executor 在 SqlSessionFactory 开启 SqlSession 时创建, 代码片段如下:
找到 Executor 以后, 我们离真相又近了一步; Executor 从设计上就考虑到了缓存, 我们可以从 createCacheKey,clearLocalCache 等方法看到其设计的巧妙之处; 这里我们先忽略缓存设计 (后面会做详细说明), 我们先来看看它的抽象类 BaseExecutor;
我们看到 BaseExecutor 对 Executor 接口进行了实现, 最终调用抽象方法 doXX; 需要交由子类去实现. 以 doUpdate,doQuery 为例, 我们来看看子类实现.
最终 MappedStatement 都是由 StatementHandler 去执行, 至此一个映射语句的完整执行流程就此结束. 当然还有一个十分重要的类 Configuration, 我们再次必须强调一下, Configuration 基本存在于整个流程, 从通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory, 到 SqlSessionFactory 中 Executor 的创建, 开启 SqlSession, 再到映射语句的执行.
Configuration
还记得我们开篇就提到过 Configuration 实例有两种构建方式, 一种是通过 XMLConfigBuilder.parse 方法, 另一种是利用 public Configuration(Environment environment) 构造方法; 所以 Configuration 从实质上来说就是 xml 的 java 对象表示.
这些元素在 Configuration 的成员变量中都可以找到. 上图参考
可能有的同学会好奇 Mapper 文件是如何配置到 Configuration 中的, 其实在构建 Configuration 实例的时候, 调用的 addMappers 方法就将 Mapper 文件注册好了, 我们通过以下代码一起来梳理一下
还记得上面执行语句的时候有两种方式, 一种是通过 SqlSession 直接执行, 一种是 getMapper 以后, 调用具体的方法.
我们在 DEBUG testQuery 时, 发现执行 mapper.selectBlog(101); 有代理对象来执行, 这是为什么呢?
这是因为我们在 session.getMapper(BlogMapper.class); 获取的实际上是 MapperRegistry 中代理工厂生产的代理对象
通过对源码的分析认识, 我们用一张流程图来总结整个流程
项目调试地址: https://github.com/sexylowrie/mybatis-teach
来源: http://www.jianshu.com/p/cdd3ab96e04d