Mybatis 源码跟踪
本篇就是记流水账的,一般孩儿可忍不了
在前面一篇博客中,我从官方文档上抄了这么一段内容,它是一段完整的mybatis执行步骤的代码。首先创建sqlSessionFactory ,在利用它获取一个SqlSession对象,这个SqlSession对象通过getMapper方法获取一个MapperProxy的代理对象,并利用代理对象执行增删改查的逻辑。
String resource = "mapper/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
BlogMapper mapper = sqlSession.getMapper(XXXX.class)
mapper.....
}finally{
sqlSession.close();
}
SqlSessionFactory
SqlSessionFactoryBuilder.build(resource) 的方法
创建SqlSessionFactory阶段最重要的就是解析所有内容。第一个Parser类解析全局配置文件,存入Configuration对象中。在解析所有的mapper.xml内容,将每一个表示SQL的语句包装成一个MappedStatement对象,这个对象包含了SQL语句的所有信息。并将所有的MappedStatement存放到Configuration中,用一个全类名Mapper.id的形式(如com.zengg.test.dao.EmployeeMapper.getEmpById)的形式一一对应,并生成一个DefaultSqlSessionFactory对象,构造器存入Configuration。
此外呢,Configuration还有一个属性MapperRegistry,这个里面一个namespace的接口类对应一个MapperProxyFactory,相当于以后的MapperProxy是用这个工厂生产的。
1、首先会创建一个XMLConfigBuilder的类对象,顾名思义,它的作用是解析全局配置文件的类,并执行解析方法。

2、XMLConfigBuilder.parse()方法中的parseConfiguration(XNode root)方法同根节点开始,将所有可能涉及到的配置标签如properties、typeAliases等信息全部封装到Configuration对象中,包括Setting里面的值,没有配置的就使用默认值填充

3、然后返回这个Configuration 对象并创建一个new DefaultSqlSessionFactory(config)
到这儿SqlSessionFactory就创建好了,它只是获取了全局配置文件的内容并新建默认对象DefaultSqlSessionFactory。
SqlSession
SqlSessionFactory.openSession()
这个阶段主要是生成一个Executor的执行器,根据configuration中的不同的executorType,生成不一样的执行器(REUSE/SIMPLE(默认)/BATCH)。如果有缓存,再包装一次成CacheExecutor(实际还是用之前生成的执行器操作,包装了一层缓存而已),然后将Executor和Configuration作为构造器的一部分,生成一个DefaultSqlSession 对象
1、在DefaultSqlSessionFactory类中执行openSession()方法,这个方法首先从全局配置对象Configuration中获取Environment标签的信息中的事务管理器和ExecutorType,并创建一个重要对象Executor(执行器),不同的ExecutorType执行器类型,会创建不同的执行器。

2、如果我们开启了二级缓存,mybatis会将已经创建的执行器包装成一个新的扩展执行器对象CachingExecutor。这个缓存执行器里面的通用的逻辑依然是用的之前创建的执行器,不过是在外加载了一层缓存相关处理的方法。

3、将Configuration、Executor 封装创建一个DefaultSqlSession对象并返回。
MapperProxy
sqlSession.getMapper(Class<T> type)
重要的就是使用MapperProxyFactory创建了MapperProxy对象,对象里包含了DefaultSqlSession(Executor和configuration)的内容
1、getMapper()方法实际上是调用了Configuration的getMapper方法(传入了sqlSession对象作为参数),再调用MapperRegistry类对象去创建。
2、在MapperReistry类中,显示根据参数MapperDao的全类名为key,获取到对应的MapperProxyFactory,然后在利用这个MapperProxyFactory新建实例MapperProxy,这个MapperProxy就是我们用的代理对象了。它底层是利用了JDK下的java.lang.reflect包去实现的。


执行查询
匹配MappedStatement,拿到语句的内容,为查询做准备。执行查询的逻辑由SqlSession执行(包含了Executor和Configuration对象)
1、MapperProxy实现了接口InvocationHandler,它执行方法的时候新进入它的invoke()方法。这个方法中传入三个参数,分别是proxy对象、方法接口以及参数。
然后创建一个MapperMethod对象并调用execute方法。
2、根据当前方法的类型(增删改查)分别执行不同的逻辑

3、当前跟踪查询方法,不同的返回参数有不同的MappedStatement执行逻辑(分页、查询条件),比如说返回值为map,那么执行结果就是 (SqlSession执行查询)
result = sqlSession.selectList(this.command.getName(), param);
如果返回值就是一个数,也会返回一个集合,只不过会再去集合的第一个值,因此它们执行查询的逻辑基本相似。

以最下面的selectOne为例(返回值为单个对象),继续跟踪。继续执行DefaultSqlSession的查询类,返回值为List并只取第一个作为结果返回。"参数中statement实际上就是sql的唯一标识全类名加配置语句的唯一id")

4、利用封装在DefaultSqlSession中的执行器Executor 进行查询逻辑。Executor有两个实现类,一个是基础实现类BaseExecutor,另外一个是CachingExecutor缓存执行器。这里我进入缓存执行器查看逻辑。
先回从configuration中对比传入的statement参数,拿到唯一的MappedStatement对象(这个对象封装了该执行语句的所有配置信息)


Executor
1、接续上面的逻辑,SqlSession调用Executor去执行的query的方法,这个方法第一步就会生成一个BoundSql的对象,这个对象的作用应该是先对我们的传参作一些封装处理(包含sql语句、传参、传参类型等等)。然后在创建了一个为查询或者保存缓存用的CacheKey对象,这玩意儿有点长(方法id,sql语句,参数信息等等),感觉啥都包含了,就是为了确认查询的唯一性的。

2、之后先执行检查缓存中有没有,没有的话使用再调用被CacheExecutor包装的真正的Executor执行查询
query方法

3、从本地缓存中拿数据(一级缓存),没有的话再重新执行查询

4、后面一直跟踪到SimpleExecutor的执行器的doQuery方法(查询完了之后的结果又会存放在localCache本地缓存中)。首先定义了一个JDBC原生的Statemten对象,也说明了底层就是根据JDBC完成的。此外,这个方法出现了非常重要的第二个接口对象StatementHandler(可使用拦截器拦截)。(注:在创建StatementHandler的时候,构造器中会默认创建另外两个重要对象PrepareHandler和ResultSethandler (BaseStatementHandler 抽象类中实现的))
StatementHandler ---> RoutingStatementHandler --> 默认PreparedStatementHandler

5、在上图中,使用prepareStatement()创建Statement对象中,需要先创建链接,然后使用RoutingStatementHandler进行参数预编译。这个预编译又依赖第三个特殊对象PrepareHandler对象进行辅助。 而PrepareHandler 又使用TypeHandler 进行参数的设置。

6、第四点中的图片,执行最后一步query后,返回的结果,又需要第四个非常重要的对象去处理返回的结果(方法在PreparedStatementHandler中),同样也用了TypeHandler辅助执行。

文中的四大对象Executor、StatementHandler、PrepareHandler和ResultSetHandler 都有一句interceptorChain.pluginAll(target)的方法用于包装它们,这也是实现Mybatis的Plugin插件的切入点。