Mybatis源码解析,一步一步从浅入深(七):执行查询

Stella981
• 阅读 643

一,前言

  我们在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码的最后一步说到执行查询的关键代码:

result = sqlSession.selectOne(command.getName(), param);

  selelectOne方法有两个参数:

  第一个参数是:com.zcz.learnmybatis.dao.UserDao.findUserById

  第二个参数是:1(Integer类型,就是我们传入的参数id:1,我们是期望通过这个id查询到我们想要的结果)

  因为接下来的代码比较复杂,而且容易迷路。那么我们就定下来本片文章的目的:

  1,sql语句是什么时候,在哪里执行的?

2,我们传入参数id是怎么参与执行的?

  为了更情绪的分析这两个问题的答案,我将分析的过程分为三步:

  1,在SqlSession中的执行过程

2,在Excutor中的执行过程

3,在Statement中的执行过程

二,在代码的执行过程中分析问题

  1,代码在SqlSession中的执行过程

在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码中,我们已经知道了,使用的sqlSession是DefaultSqlSession,那显而易见,要首先看一下selectOne的源码了:

 1 DefaultSqlSession implements SqlSession
 2 public <T> T selectOne(String statement, Object parameter) {
 3     // Popular vote was to return null on 0 results and throw exception on too many.
 4     // 执行查询
 5     List<T> list = this.<T>selectList(statement, parameter);
 6     if (list.size() == 1) {
 7     //如果返回的list的大小是1,则返回第一个元素
 8       return list.get(0);
 9     } else if (list.size() > 1) {
10     //如果大于1,则抛出异常
11       throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
12     } else {
13     // 如果小于1,则返回null
14       return null;
15     }
16   }

  到这里可以明白一件事情,原来selectOne是调用selectList执行查询的,只不过是取了返回值的第一个元素。  

  我们传入的参数id,就是第5行代码的第二个参数,继续分析第5行的源代码:

DefaultSqlSession implements SqlSession
public <E> List<E> selectList(String statement, Object parameter) {
    // 调用了另外一个三个参数的重载方法,
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

    继续跟踪:

 1  DefaultSqlSession implements SqlSession
 2  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
 3     try {
 4       //从configuration中取出解析userDao-mapping.xml文件是生成的MappedStatement
 5       MappedStatement ms = configuration.getMappedStatement(statement);
 6       // 这里的executor是CachingExecutor
 7       List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
 8       return result;
 9     } catch (Exception e) {
10       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
11     } finally {
12       ErrorContext.instance().reset();
13     }
14   }

  看源代码的第5行,还记得在文章:Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析中,mapper文件中的每一个select,insert,update,delete标签,都会解析成一个MappedStatement类的实例对象吗?而且这个实例对象是存放在configuration中的。然后执行第7行代码,这里的executor是CachingExecutor实例对象。到这里SqlSession中的代码流程就结束了,我们进入Executor中的执行过程。

2,代码在在Excutor中的执行过程

看看源代码:

1   CachingExecutor implements Executor
2   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
3     //取出sql语句
4     BoundSql boundSql = ms.getBoundSql(parameterObject);
5     CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
6     return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
7   }

  同样的,查询参数id也是在这个方法的第二个参数,而且在取出sql语句的方法中,对查询参数进行了检查。继续跟踪这个方法中的第6行代码:

 1    CachingExecutor implements Executor
 2    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
 3       throws SQLException {
 4     //使用缓存  
 5     Cache cache = ms.getCache();
 6     if (cache != null) {
 7       flushCacheIfRequired(ms);
 8       if (ms.isUseCache() && resultHandler == null) {
 9         ensureNoOutParams(ms, parameterObject, boundSql);
10         @SuppressWarnings("unchecked")
11         List<E> list = (List<E>) tcm.getObject(cache, key);
12         if (list == null) {
13           list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
14           tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
15         }
16         return list;
17       }
18     }
19     
20     return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
21   }

  继续跟踪第20行代码:

 1 BaseExecutor implements Executor
 2   @SuppressWarnings("unchecked")
 3   public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 4     ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
 5     if (closed) throw new ExecutorException("Executor was closed.");
 6     if (queryStack == 0 && ms.isFlushCacheRequired()) {
 7       clearLocalCache();
 8     }
 9     List<E> list;
10     try {
11       queryStack++;
12       list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
13       if (list != null) {
14         handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
15       } else {
16         list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
17       }
18     } finally {
19       queryStack--;
20     }
21     if (queryStack == 0) {
22       for (DeferredLoad deferredLoad : deferredLoads) {
23         deferredLoad.load();
24       }
25       deferredLoads.clear(); // issue #601
26       if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
27         clearLocalCache(); // issue #482
28       }
29     }
30     return list;
31   }

  继续跟踪第16行代码:   

 1  BaseExecutor implements Executor
 2    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 3     List<E> list;
 4     localCache.putObject(key, EXECUTION_PLACEHOLDER);
 5     try {
 6       list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 7     } finally {
 8       localCache.removeObject(key);
 9     }
10     localCache.putObject(key, list);
11     if (ms.getStatementType() == StatementType.CALLABLE) {
12       localOutputParameterCache.putObject(key, parameter);
13     }
14     return list;
15   }

  继续跟踪第6行代码:

 1   SimpleExecutor extends BaseExecutor 
 2    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 3     Statement stmt = null;
 4     try {
 5       Configuration configuration = ms.getConfiguration();
 6       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 7       //准备预处理语句
 8       stmt = prepareStatement(handler, ms.getStatementLog());
 9       return handler.<E>query(stmt, resultHandler);
10     } finally {
11       closeStatement(stmt);
12     }
13   }

  看到第3行了吗?这里声明了一个Statement,是不是很熟悉?是的,这就是我们在使用原始的JDBC进行查询的时候,用到的,那么我们的问题是不是在这里就有了答案了呢?这里先留一个标记。

  代码执行到这里之后呢,Executor部分的流程就结束了,接下来是在Statement中的执行过程。

  3,代码在Statement中的执行过程

继续看源码:

 1  //第一段源码
 2   RoutingStatementHandler implements StatementHandler
 3    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
 4     return delegate.<E>query(statement, resultHandler);
 5   }
 6   //第二段源码
 7   PreparedStatementHandler extends BaseStatementHandler
 8    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
 9     PreparedStatement ps = (PreparedStatement) statement;
10     ps.execute();
11     return resultSetHandler.<E> handleResultSets(ps);
12   }

  在源代码的第10行,是不是也很熟悉?同样也是使用了JDBC进行的查询。似乎这一段的源代码很简单,但其实不是的resultSetHandler中也是做了一部分工作的,这里就不详细描述了。代码到这里就结束了,似乎我们没有看到文章开头的两个问题的答案。看来应该就是在上面标记的地方了。

  4,问题的答案

    关键代码:stmt = prepareStatement(handler, ms.getStatementLog());

    prepareStatement源码:

 1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 2     Statement stmt;
 3     //从事务中获取数据库连接
 4     Connection connection = getConnection(statementLog);
 5     // 获取Statement
 6     stmt = handler.prepare(connection);
 7     // 为Statement设置查询参数
 8     handler.parameterize(stmt);
 9     return stmt;
10   }

 1 public Statement prepare(Connection connection) throws SQLException {
 2     ErrorContext.instance().sql(boundSql.getSql());
 3     Statement statement = null;
 4     try {
 5       //初始化Statement
 6       statement = instantiateStatement(connection);
 7       //设置查询超时时间
 8       setStatementTimeout(statement);
 9       setFetchSize(statement);
10       return statement;
11     } catch (SQLException e) {
12       closeStatement(statement);
13       throw e;
14     } catch (Exception e) {
15       closeStatement(statement);
16       throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
17     }
18   }

 1 protected Statement instantiateStatement(Connection connection) throws SQLException {
 2     String sql = boundSql.getSql();
 3     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
 4       String[] keyColumnNames = mappedStatement.getKeyColumns();
 5       if (keyColumnNames == null) {
 6         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
 7       } else {
 8         return connection.prepareStatement(sql, keyColumnNames);
 9       }
10     } else if (mappedStatement.getResultSetType() != null) {
11       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
12     } else {
13         //从数据库连接中获取statement
14       return connection.prepareStatement(sql);
15     }
16   }

  看到第14行,哈哈这里就是我们要的答案了。这样一来我们的两个问题,就都有答案了。

  因为执行查询的这个过程比较复杂,如果真的要详细的全部解释清楚的话,估计还得10几篇文章要写。鉴于作者能力和时间,就大概的展示一下这个过程吧。


 远程不易,转载请声明出处:https://www.cnblogs.com/zhangchengzi/p/9712446.html

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Java修道之路,问鼎巅峰,我辈代码修仙法力齐天
<center<fontcolor00FF7Fsize5face"黑体"代码尽头谁为峰,一见秃头道成空。</font<center<fontcolor00FF00size5face"黑体"编程修真路破折,一步一劫渡飞升。</font众所周知,编程修真有八大境界:1.Javase练气筑基2.数据库结丹3.web前端元婴4.Jav
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Mybatis源码解析,一步一步从浅入深(五):mapper节点的解析
在上一篇文章Mybatis源码解析,一步一步从浅入深(四):将configuration.xml的解析到Configuration对象实例(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.cnblogs.com%2Fzhangchengzi%2Fp%2F9674527.html)
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这