MyBatis源码学习—SqlSession的运行过程

SqlSession的运行过程是整个MyBatis最难以理解的部分。SqlSession是一个接口,使用它并不复杂。我们构建SqlSessionFactory之后就可以轻易地拿到SqlSession了。SqlSession 接口给出了查询、插入、更新、删除的方法,在旧版本的MyBatis 或iBatis中常常使用这些接口方法,而在新版的MyBatis-3中我们建议使用Mapper,SqlSession作为MyBatis最为常用和重要的接口之一,我们有必要学习一下其中的设计思路。

1、构建SqlSession对象

首先我们先来看一段代码:

public class SysConfigService {

    public static void main(String[] args) throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        try(SqlSession sqlSession = sqlSessionFactory.openSession()){
            SysConfigMapper mapper = sqlSession.getMapper(SysConfigMapper.class);
            mapper.selectAll().forEach(System.out::println);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在我的上一篇博文:MyBatis源码学习—SqlSessionFactory的构建过程中讨论了MyBatis的SqSessionFactory的构建过程,有了SqlSeesionFactory我们就可以很轻松的使用sqlSessionFactory.openSession()一句代码构建一个SqlSession对象了,那么SqlSession的构建过程中有发生了那些事情呢?我们继续追踪源码~~

跟随源码,首先我们来到SqlSeesionFactory接口的默认实现类DefaultSqlSessionFactory内部。如下图所示。

openSession()方法调用了另一个方法openSessionFromDataSource,也即使用过数据库数据源配置获取SqlSession对象;除此之外,DefaultSessionFactory还提供了另一种获取SqlSession对象的方法:openSessionFromConnection,即我们还可以给MyBatis一个和数据库建立好的连接Connection对象来创建SqlSession对象,如下图所示。

好的,回到主题来。我继续追到openSessionFromDataSource方法中看看MyBatis为我们做了哪些工作,如下图所示。

方法传入三个参数:ExecutorType、TransactionIsolationLevel和一个boolean类型的表示autoCommit

  • ExecutorType:执行器的类型,是个枚举类,定义了三种类型的执行器:SIMPLE、REUSE、BATCH,分别对应MyBatis中三种不同类型的执行器。详细内容请参考我的另一篇博文:MyBatis源码学习—MyBatis中的ExecutorType
  • TransactionIsolationLevel:事务隔离级别。MyBatis中定义的事物隔离级别和MySQL、Spring的事物隔离级别是一致的,详细内容请参考我的另一篇博文:Spring从入门到精通—Spring事务详解
  • 参数autoCommit:指示是否开启自动提交事务,默认是关闭的,也即不自动提交事务。

方法的执行流程大致如下:

  1. 通过configuration对象获取到配置信息Enviroment
  2. 使用Enviromenth获取创建事务管理工厂,并通过事务工厂获取到事物管理器JdbcTransaction
  3. 根据执行器类型ExecutorType创建指定执行器,默认的执行器是SimpleExecutor
  4. 获得执行器对象之后,MyBatis直接为我们new了一个SqlSeesion的默认实现类DefaultSqlSession
Executor创建细节简单分析

MyBatis通过调用configuration对象的newExecutor方法获取到执行器实例,如下图所示。

方法开始,检查如果没有指定执行器类型,如果没有指定执行器类型,MyBatis就会选择使用默认的执行器SimpleExecutor,之后MyBatis就会通过一个参数cacheEnabled来判断是否开启二级缓存(不清楚的老铁才可以参考我的另一篇博文:深入理解MyBatis缓存机制),因此new了一个CacheingExecutor包装了一下executor。

顺便提一嘴,对于一级缓存/session级缓存MyBatis默认是开启的,并且不可控制,它是在BaseExecutor中使用PerpetualCache类型的localCache字段控制的,如下图所示。

2、映射器的动态代理

到这里,我们就获得了一个SqlSession对象,通过SqlSession对象我们就可以调用getMapper()获取到Mapper接口的“实例”,进而执行sql查询了,如下图所示。

正如我们看到的那样,在使用MyBatis的时候我们只需要一个Mapper接口,然后使用SqlSession的getMapper()方法就可以获得这个Mapper接口的实例,进而可以实现对数据库的CRUD,但Mapper 仅仅是一个接口,而不是一个包含逻辑的实现类。我们知道一个接口是没有办法去执行的,那么它是怎么运行的呢?这不是违反了教科书所说的接口不能运行的道理吗?相信有很多同学对此有疑惑,并且在面试中被面试官这样问过。

答案就是动态代理。我们不妨先来看看Mapper是个什么东西。如下图所示。

原来是MyBatis为Mapper接口自动生成了一个代理对象。原理就是使用jdk动态代理通过反射为我们创建了代理对象,具体看源码吧。

从我们代码的getMapper进去,这里封装了一下,直接调用了configuration对象的getMapper方法

configuration对象的getMapper方法也是进行了封装,直接调用了MapperRegistry类的getMapper方法。注:(MapperRegistry是一个专门管Mapper注册的一个类,这一步在构建SqlSessionFactory的时候就初始化了,详情参考MyBatisXMLConfigBuilder类的mapperElement方法)

MapperRegistry中核心的就是那句mapperProxyFactory.newInstance(sqlSession),它调用了Mapper代理工厂的newInstance方法

如下图所示,我们可以看到MapperProxyFactory中使用了jdk动态代理为Mapper接口创建了代理对象。关于jdk动态代理的原理可以参考我的另一篇博文:透过代理模式探究JAVA的静态代理和动态代理

我们看到,在调用jdk动态代理之前,MyBatis先new了一个MyBatisProxy对象,这是啥呢?其实MapperProxy就是jdk动态代理的重要接口InvocationHandler的一个实现类,被代理的方法全部都在MapperProxy这个类中

MapperProxy细节

​ MyBatis在MapperProxyFctory中调用了Proxy.newProxyInstance方法产生了Mapper接口的代理对象,代理对象的全部方都在MapperProxy这个类中,我们来看看。

​ 上面运用了invoke 方法。invoke方法会拦截Mapper中的所有方法。拦截之后invoke首先判断method如果是Object中定义的方法,直接执行。如toString(),hashCode()等,其他Mapper接口定义的方法交由MapperMethodInvoker来执行。

​ MapperMethodInvoker是一个接口,它在MapperProxy这个类中有两个实现类DefaultMethodInvokerPlainMethodInvoker。这两个类都实现了MapperMethodInvoker的invoke方法,那么如何调用呢?代码写的很清楚了,如果方法是默认方法,那就调用DefaultMethodInvoker的invoke方法,否则如果不是默认默认方法,那就调用PlainMethodInvoker实现的invoke方法。读到这里有些同学就有疑问了,上面是默认方法呢?这个是jdk中的一个方法,我们来看一下jdk的解释,如下图所示。

​ 我用谷歌翻译翻了一下,意思是:默认方法是公共非抽象实例方法,即是在接口中声明的带有主体的非静态方法类型。也就是说,默认方法就是我们说的成员方法,在Mapper中定义的抽象方法不属于默认方法,那自然就会调用PlainMethodInvoker实现的invoke方法

​ 然后在PlainMethodInvoker实现的invoke方法中调用了MapperMethod的execute方法执行SQL语句.如下图所示,我们可以看到它首先判断了一下SQL语句的类型,让后按照不同类型的sql语句执行不同的代码。

方法还是很多的,我们不需要全看,看一个常用的查询返回多条记录的方法executorForMany,如下图所示。

MapperMethod采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中,我们不需要全部明白。我们可以看到里面的executeForMany方法,再看看它的实现,实际上它最后就是通过sqlSession对象去运行对象SQL。

​ 至此,相信大家已经了解了MyBatis为什么只用Mappper接口便能够运行SQL,因为映射器的XML文件的命名空间对应的便是这个接口的全路径,那么它根据全路径和方法名便能够绑定起来,通过动态代理技术,让这个接口跑起来。而后采用命令模式,最后还是使用SqlSession 接口的方法使得它能够执行查询,有了这层封装我们便可以使用接口编程,这样编程就更简单了。

3、SqlSession四大对象

​ 现在我们已经知道了映射器其实就是一个动态代理对象,进入到了MapperMethod的execute方法。它经过简单判断就进入了SqlSession 的删除、更新、插入、查询等方法,那么这些方法如何执行呢?这是我们需要关心的问题,也是正确编写插件的根本。

​ 显然通过类名和方法名字就可以匹配到我们配置的SQL,我们不需要去关心这些细节,我们关心的是设计架构。Mapper 执行的过程是通过Excutor 、StatementHandler、ParameterHandler和ResultHandler来完成数据库操作和结果返回的。

  • Executor表示执行器,由他调度StatementHandler、ParameterHandler和ResultHandler等来执行SQL语句
  • StatementHandler使用JDBC的Statement或PreparedStatement来执行操作,他是四大对象的核心,器承上启下的作用。
  • ParameterHandler用于SQL参数的处理。
  • ResultHandler对JDBC的ResultSet进行封装处理并返回。

了解到这里我们在看一下MyBatis处理流程图,如下图所示。

构建SqlSession的过程在MyBatis源码学习—SqlSessionFactory的构建过程我们已经学习了,创建SqlSession的流程在本节也进行了学习。下面我们将逐一分析学习剩下的四个对象的生成和运作原理。到这里我们已经来到了MyBatis的底层设计,对Java语言基础不牢的同学来说,这将是一次挑战。详细的内容可以参考我的博文:

留言区

还能输入500个字符