Mybatis源码分析-Mybatis是如何执行sql的
使用mybatis作为dao层框架是目前较主流的一种方案,与spring框架整合下的实践一般是先编写mapper接口,再编写mapper的xml文件,最后在service层中调用mapper接口进行数据库层面操作。举个系统岗位实例的curd做例子:
- mapper接口
1 | public interface SysPostMapper |
- mapper的xml文件
1 |
|
- service层调用
1 |
|
举例完毕,本文要探究的是为什么调用mapper接口就可以操作数据库?
1 mapper接口不是接口,是动态代理类
接口自然是无法直接调用,真正在调用的其实是增强了该接口的动态代理类。mybatis使用的动态代理是基于jdk自带的那种,即基于接口的。下面举个例子说明下:
- mapper接口
1 | public interface StudentMapper { |
- 增强器(即实现了InvocationHandler)
1 | public class MapperProxy<T> implements InvocationHandler { |
- demo演示
1 | public class Demo { |
studentMapper是个接口,也没有任何实现类,但也可直接调用,因为实际调用的是动态代理类,其具体逻辑定义在MapperProxy的invoke方法中。例子虽简单,但mybatis的做法其实也是类似的,下面一起探究。
首先从DefaultSqlSession类的getMapper方法切入,其实与spring整合的场景下一般感知不到这个步骤,因为mapper的动态代理类已经交给spring容器管理了。
1 | // org.apache.ibatis.session.defaults.DefaultSqlSession |
1 | // org.apache.ibatis.session.Configuration |
1 | // org.apache.ibatis.binding.MapperRegistry |
1 | // org.apache.ibatis.binding.MapperProxyFactory |
看看MapperProxy的具体逻辑把
1 | public class MapperProxy<T> implements InvocationHandler, Serializable { |
由上面的源码分析可知,mapper接口执行的实际逻辑定义在MapperProxy的invoke方法中
2 mapper接口执行查询类sql的过程
2.1 mapper接口的执行入口
上面说过mapper接口是个动态代理类,分析过增强的逻辑即可知道真正执行逻辑封装在类MapperMehod中,故从此类切入
1 | // org.apache.ibatis.binding.MapperMethod |
1 | // org.mybatis.spring.SqlSessionTemplateorg.mybatis.spring.SqlSessionTemplate |
2.2 获取SqlSession
说明下获取sqlSession的过程
1 | // org.mybatis.spring.SqlSessionUtils |
TransactionSynchronizationManager这个对象是spring-tx包下的,与spring的事务相关,可存放很多线程相关的变量,值得关注,后期将mybatis和事务结合起来的时候再重点分析。
接下来重点分析下openSession的过程
1 | //org.apache.ibatis.session.defaults.DefaultSqlSessionFactory |
session中必然要有数据库的信息,transaction封装了dataSource,executor中封装Transaction,最终在DefaultSqlSession中封装executor,链式调用,最终就有此信息
看看获取Executor的过程
1 | //org.apache.ibatis.session.Configuration |
2.2.1 值得关注的插件增强
用过分页插件的都知道其原理是增强了executor的实现类,增强的插入点就在这里:
1 | //org.apache.ibatis.plugin.InterceptorChain |
总结:executor是有可能被插件增强过的。
上面这一大段都是如何获取sqlSession的逻辑,为啥这么复杂呢?因为与spring整合后有事务这种概念,同个事务使用相同的sqlSession,引入了spring-tx中的组件TransactionSynchronizationManager用于获取同个线程中先前存过的sqlSession(如果存在)。梳理下来有这么几点:
- 获取组件:
executortransactionsqlsession - 组件的依赖,
executor需要transaction,transaction中封了datasource的信息,sqlSession需要executor - 其他的点:
- 如果
TransactionSynchronizationManager有SqlSession支持直接返回,没有得新获取 executor是可以被plugin增强的
- 如果
2.3 sqlSession的查询逻辑
有了sqlSession总算可以查数据了,下面开始分析!
回到逻辑:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
通过反射的方式调用类org.apache.ibatis.session.defaults.DefaultSqlSession的selectList方法
1 | private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { |
经过多个重载方法后最终进入executor.query(ms, wrapCollection(parameter), rowBounds, handler);
如果statement有使用cache则会先从cache中取,即配置二级缓存,不过目前没遇到配cache的情况,故简化处理,具体逻辑在cachingExecutor的deltegate成员,即simpleExecutor中
1 |
|
总结下获取数据的过程:一般都是通过simpleExecutor此组件获取数据,先从一级缓存取,没有则从数据库取,然后放入缓存中;
2.4 StatemnetHandler组件真干活
从获取数据到封装数据有个很重要的组件StatementHandler,接下来重点说明:
2.4.1 获取StatemnetHandler过程
1 | // org.apache.ibatis.session.Configuration |
2.4.2 基于StatementHandler获取prepareStatement
1 | private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { |
总结下获取statement的过程:
- 获取connection
- 通过connection和
sql获取prepareStatement - 将参数填充到statement中,参数已经在
mappedStatemnt的bounedSql的ParameterMappings中封好,依次遍历封装进去
2.4.3 使用StatementHandler获取数据
1 | // org.apache.ibatis.executor.statement.PreparedStatementHandler |
上面分析了使用preparedStatementHandler获取对象的具体过程,总结成下面几点:
- 调用statement.execute,其实就是进行查库
- 使用
DefaultResultSetHandler的handleResultSets方法处理数据,其实就是遍历resultset,通过反射方式创建对象,设置对象值 - 理一下几个组件:
metaObjectrowValueDefaultResultHandler的list变量multipleResults,先创建对象作为rowValue,把rowValue封到metaObject中,根据resultSet封属性的时候操作的是metaObject,那么其实也封到了rowValue中,rowValue后面会封到DefaultResultHandler的list变量,从DefaultResultHandler的list变量赋值到multipleResults后返回
到此,获取数据的整个过程结束,后续操作一般是善后类的,如关闭sqlSession之类
3 mapper接口执行更新类sql的过程
前面介绍了查询语句的逻辑,下面介绍下更新语句的逻辑,有了上面的分析过程,看update就轻松很多。
还是从MapperProxy切入
1 | // org.apache.ibatis.binding.MapperMethod |
mapperMethod最终也是调用DefaultSqlSession的update方法,具体分析下此方法
1 | // org.apache.ibatis.session.defaults.DefaultSqlSession |
1 | // org.apache.ibatis.executor.SimpleExecutor |
获取prepareStatement的过程和查询是一样的,三步走:1获取连接connection,2基于connection生成prepareStatement,3使用statementHandler填充prepareStatement中的参数
下面具体分析statementHandler的update逻辑
1 | // org.apache.ibatis.executor.statement.PreparedStatementHandler |
更新操作的逻辑很简单,没有查询那么复杂。关于key生成器的使用虽然update操作没用到,但是insert操作时经常使用,具体表现就是如果主键是自增的,则插入完成后可将自增生成的主键自动设置到对象中,下面具体分析下其逻辑
3.1 关于KeyGenerator
keyGenerator是一个接口,mybatis提供了几个实现类:
Jdbc3KeyGenerator自增主键那一类使用这种SelectKeyGeneratororacle那类需要指定selectKey的使用这种NoKeyGenerator空操作
分析下Jdbc3KeyGenerator 的逻辑
1 |
|
4 sql执行过程总结
画图总结

5 最后
本文从源码角度分析了mybatis执行sql的过程,可能有不严谨和理解不正确的地方,在以后有更多使用经验后不断修正。
参考文章:
