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
(如果存在)。梳理下来有这么几点:
- 获取组件:
executor
transaction
sqlsession
- 组件的依赖,
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,通过反射方式创建对象,设置对象值 - 理一下几个组件:
metaObject
rowValue
DefaultResultHandler的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
自增主键那一类使用这种SelectKeyGenerator
oracle
那类需要指定selectKey
的使用这种NoKeyGenerator
空操作
分析下Jdbc3KeyGenerator
的逻辑
1 |
|
4 sql执行过程总结
画图总结
5 最后
本文从源码角度分析了mybatis执行sql的过程,可能有不严谨和理解不正确的地方,在以后有更多使用经验后不断修正。
参考文章: