mybatis源码分析(五)------------SQL的执行过程
在对SQL的执行过程进行分析前,先看下测试demo:
/**
* @author chenyk
* @date 2018年8月20日
*/ public class GoodsDaoTest { private static SqlSessionFactory sqlSessionFactory = null; @Test
public void selectGoodsTest(){
SqlSession sqlSession = getSqlSessionFactory().openSession(true); // 1.加载mybatis配置文件 2.加载Mapper映射文件
GoodsDao goodsMapper = sqlSession.getMapper(GoodsDao.class); // 3.使用JDK动态代理的方式生成Mapper的代理对象
goodsMapper.selectGoodsById("1"); // 4.SQL的执行过程
sqlSession.commit();
} public static SqlSessionFactory getSqlSessionFactory() {
String resource = "spring-ibatis.xml";
if(sqlSessionFactory == null){
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources
.getResourceAsReader(resource));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return sqlSessionFactory;
} }
整个的流程就是上面注释部分所写,加载配置文件------》加载映射文件-------》为Mapper接口生成代理对象----------》调用方法时,真正的执行逻辑是在invoke方法中。这篇文章就从MapperMethod类的execute方法入手,来分析SQL的执行过程。对于前面三个过程在其他文章中已经做了分析。好了,直接进入execute方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// insert操作
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
// update操作
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
// delete操作
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
// select操作:返回类型为void,同时使用了ResultHandler
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// select操作:返回多条记录,集合或者数组
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// select操作:返回Map结构
result = executeForMap(sqlSession, args);
} else {
// select操作:返回一条记录,其实还是使用了selectList
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
1.查询
我们先从查询操作分析,对于executeWithResultHandler,executeForMany,executeForMap这些方法,都是调用了对应sqlSession.select*的方法,sqlSession提供了各种不同的select方法。比如executeForMany,底层调用的是sqlSession.selectList方法,如果返回的结果类型是LIst,那么直接返回就可以了,如果返回的是数组或者set,那么在executeForMany方法中,会对List进行包装,然后返回。可以看下源码:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
// 查询的结果类型是List
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
// 如果有分页
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
// 如果方法中定义的返回类型不是List,再进行判断
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
// 如果方法中定义的返回类型是数组,那么对查询的结果List转为数组
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
// 否则,按照方法中定义的返回类型,将List进行转换
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
对于查询操作,其原理都大同小异,所以我们将以其中一个的select操作进行分析整个过程,然后再针对整个过程中的涉及到重要节点进行分析。可以这样理解:一棵大树,我们从底部到顶部对主干先大致分析一遍,然后再对主干上的比较粗的枝干进行分析。接下里,就对selectOne这个主干进行分析。
1.1 selectOne方法分析
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
// 还是调用的selectList方法,只是返回第一条记录
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
// 如果查询出的数据有多条,则会返回报错信息
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
进入selectList方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用Executor实现类中的query方法
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
return result;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里有个问题值得思考:此处的query方法是调用Executor的哪个实现类的呢?默认是调用CachingExecutor的query方法,这个可以从sqlSessionFactory.openSession这个过程可以看出。CachingExecutor是个装饰类,用于给目标类增加二级缓存功能,那么目标类就是SimpleExecutor。关于Executor这一块在后面会做讲解。
进入CachingExecutor中的query方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创建CacheKey
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 进入此方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从MappedStatement中获取缓存,如果映射文件中没有配置缓存,则此处cache==null
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 二级缓存未命中,则调用被装饰类的query方法:SimpleExecutor的query方法,它继承了BaseExecutor 进入此方法
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将查询结果放到缓存中
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
}
return list;
}
}
// 进入此方法
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
由于SimpleExecutor继承了BaseExecutor,并没有对query方法重新,所以进入BaseExecutor的query方法:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从一级缓存中获取缓存项
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 如果一级缓存未命中,则从数据库中查询 进入此方法
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
上面这个方法主要是先从一级缓存中获取数据,如果缓存未命中,则再查询数据库,接下来进入queryFromDatabase方法:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 向缓存中存储一个占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 数据库查询操作
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 移除占位符
localCache.removeObject(key);
}
// 缓存查询的结果
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
然后我们进入SimpleExecutor的doQuery方法,进行真正的数据库查询操作:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 查询操作
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
接下来,进入PreparedStatementHandler类的query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行SQL
ps.execute();
// 处理查询结果
return resultSetHandler.<E> handleResultSets(ps);
}
以上就是mybatis查询的主要过程,主干部分理清楚了,接下来就要对主干上的重要的枝干部分进行一个个的分析。
1.2 Executor解析
我们应该还记得上面提到的Executor接口的实现类是指CachingExecutor这个装饰类,它装饰的目标类是SimpleExecutor,我们从何处得知呢?首先进入DefaultSqlSession,Executor接口是它的一个属性,在DefaultSqlSession构造函数中给Executor赋予了值,所以搞清楚在DefaultSqlSession实例化构造对象时,传入的Executor类型就可以了。进入DefaultSqlSessionFactory的openSession方法:
public SqlSession openSession() {
// 进入此方法
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取environment
final Environment environment = configuration.getEnvironment();
// 根据environment获取transactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 获取transaction对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 实例化一个Executor对象,此处的execType默认是SIMPLE 进入此方法
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
进入configuration.newExecutor方法,重点部分在这:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 由于executorType==SIMPLE
executor = new SimpleExecutor(this, transaction);
}
// 如果映射文件配置了二级缓存,则对SimpleExecutor进行装饰,而装饰类是CacheExecutor,这就是创建DefaultSqlSession时传入的Executor参数
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
好了,现在对于Executor的实现类为什么是CachingExecutor,已经清楚了。对于Executor的介绍可以看下这篇文章:《Executor介绍》。
未完待续。。。
mybatis源码分析(五)------------SQL的执行过程的更多相关文章
- MySQL源码分析之SQL函数执行
1.MySQL中执行一条SQL的总体流程 2.SQL函数执行过程 1.MySQL中执行一条SQL的总体流程 一条包含函数的SQL语句,在mysql中会经过: 客户端发送,服务器连接,语法解析,语句执行 ...
- mybatis源码解读(五)——sql语句的执行流程
还是以第一篇博客中给出的例子,根据代码实例来入手分析. static { InputStream inputStream = MybatisTest.class.getClassLoader().ge ...
- MyBatis 源码分析——动态SQL语句
有几年开发经验的程序员应该都有暗骂过原生的SQL语句吧.因为他们不能一句就搞定一个业务,往往还要通过代码来拼接相关的SQL语句.相信大家会理解SQL里面的永真(1=1),永假(1=2)的意义吧.所以m ...
- 精尽 MyBatis 源码分析 - SqlSession 会话与 SQL 执行入口
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- MyBatis源码分析-SQL语句执行的完整流程
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简 ...
- 精尽MyBatis源码分析 - MyBatis 的 SQL 执行过程(一)之 Executor
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(三)之 ResultSetHandler
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- 精尽MyBatis源码分析 - SQL执行过程(四)之延迟加载
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
- Mybatis源码分析之Cache二级缓存原理 (五)
一:Cache类的介绍 讲解缓存之前我们需要先了解一下Cache接口以及实现MyBatis定义了一个org.apache.ibatis.cache.Cache接口作为其Cache提供者的SPI(Ser ...
随机推荐
- Java开发笔记(六十四)静态方法引用和实例方法引用
前面介绍了方法引用的概念及其业务场景,虽然在所列举的案例之中方法引用确实好用,但是显而易见这些案例的适用场合非常狭窄,因为被引用的方法必须属于外层匿名方法(即Lambda表达式)的数据类型,像isEm ...
- Java高阶语法---Volatile
背景:听说Volatile Java高阶语法亦是挺进BAT的必经之路. Volatile: volatile同步机制又涉及Java内存模型中的可见性.原子性和有序性,恶补基础一波. 可见性: 可见性简 ...
- 微信小程序异步请求问题
微信小程序为了提高用户体验,提供的api大部分都是异步操作,除了数据缓存操作里面有一些同步操作.是提高了用户体验,但是在开发的时候, 就有点坑了,例如我要写一个公共方法,发起网络请求,去后台去一些数据 ...
- 智能化CRM客户关系管理系统介绍一
智能化CRM客户关系管理系统介绍一 CRM客户关系管理的定义是:企业为提高核心竞争力,利用相应的信息技术以及互联网技术来协调企业与顾客间在销售.营销和服务上的交互,从而提升其管理方式,向客户提供创新式 ...
- Mapbox浅析(快速入门Mapbox)
1.是什么? Mapbox是一个可以免费创建并定制个性化地图的网站. 2.了解一些基本东西 常见的 mapbox.js和mapbox-gl.js的异同点? 相同点: 1.都是由Mapbox公司推出的免 ...
- 关于使用阿里OSS服务搭建图床和使用PicGO上传图片到图床
最近喜欢上了使用markdown来写博客,可是markdown的图片却是本地的,如果我要发博客,那么又要重复截图了.于是干脆弄了个图床,本地截图的时候上传到图床,markdown中的代码结果也是图床里 ...
- ASP.NET Zero--前端应用程序
前端应用程序 ASP.NET Zero包含可以作为您的公共网站或应用程序着陆页的起点的前端页面.首次运行项目时,您会看到主页如下所示: 这里有两页:主页和关于.这些页面的内容只是占位符和演示目的.您可 ...
- python2.7生成exe可执行文件
1.安装对应python版本的py2exe py2exe下载地址 2.假设你要生成test.py脚本的exe文件 新建一个setup.py,在里面输入如下代码 #!/usr/bin/python fr ...
- 含有package关键字的java文件在命令行运行报错
程序中含有package关键字,使用命令行运行程序时出现"找不到或无法加载主类",而使用Eclipse软件可以正常运行程序的可能解决办法. 在包下的类,在Java源文件的地方编译后 ...
- 2017 百度杯丶春秋欢乐赛 writeup
1. 内涵图(Misc) 题目: 我不是一个简单的图片 我是一个有内涵的图片 解:保存到桌面,右键属性->详细信息,即可获得flag. 2. 小电影(Misc) 题目: 我说过 这次比赛是让大家 ...