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

一,前言

  我们在文章: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 1 DefaultSqlSession implements SqlSession 2 2 public <T> T selectOne(String statement, Object parameter) { 3 3 // Popular vote was to return null on 0 results and throw exception on too many. 4 4 // 执行查询 5 5 List<T> list = this.<T>selectList(statement, parameter); 6 6 if (list.size() == 1) { 7 7 //如果返回的list的大小是1,则返回第一个元素 8 8 return list.get(0); 9 9 } else if (list.size() > 1) { 1010 //如果大于1,则抛出异常 1111 throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); 1212 } else { 1313 // 如果小于1,则返回null 1414 return null; 1515 } 1616 }

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

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

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

    继续跟踪:

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

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

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

看看源代码:

11 CachingExecutor implements Executor 22 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 33 //取出sql语句 44 BoundSql boundSql = ms.getBoundSql(parameterObject); 55 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); 66 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 77 }

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

1 1 CachingExecutor implements Executor 2 2 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 3 3 throws SQLException { 4 4 //使用缓存 5 5 Cache cache = ms.getCache(); 6 6 if (cache != null) { 7 7 flushCacheIfRequired(ms); 8 8 if (ms.isUseCache() && resultHandler == null) { 9 9 ensureNoOutParams(ms, parameterObject, boundSql); 1010 @SuppressWarnings("unchecked") 1111 List<E> list = (List<E>) tcm.getObject(cache, key); 1212 if (list == null) { 1313 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 1414 tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks 1515 } 1616 return list; 1717 } 1818 } 1919 2020 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 2121 }

  继续跟踪第20行代码:

1 1 BaseExecutor implements Executor 2 2 @SuppressWarnings("unchecked") 3 3 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 4 4 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 5 5 if (closed) throw new ExecutorException("Executor was closed."); 6 6 if (queryStack == 0 && ms.isFlushCacheRequired()) { 7 7 clearLocalCache(); 8 8 } 9 9 List<E> list; 1010 try { 1111 queryStack++; 1212 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 1313 if (list != null) { 1414 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 1515 } else { 1616 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 1717 } 1818 } finally { 1919 queryStack--; 2020 } 2121 if (queryStack == 0) { 2222 for (DeferredLoad deferredLoad : deferredLoads) { 2323 deferredLoad.load(); 2424 } 2525 deferredLoads.clear(); // issue #601 2626 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 2727 clearLocalCache(); // issue #482 2828 } 2929 } 3030 return list; 3131 }

  继续跟踪第16行代码:   

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

  继续跟踪第6行代码:

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

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

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

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

继续看源码:

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

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

  4,问题的答案

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

    prepareStatement源码:

1 1 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { 2 2 Statement stmt; 3 3 //从事务中获取数据库连接 4 4 Connection connection = getConnection(statementLog); 5 5 // 获取Statement 6 6 stmt = handler.prepare(connection); 7 7 // 为Statement设置查询参数 8 8 handler.parameterize(stmt); 9 9 return stmt; 1010 } 11 12 1 public Statement prepare(Connection connection) throws SQLException { 13 2 ErrorContext.instance().sql(boundSql.getSql()); 14 3 Statement statement = null; 15 4 try { 16 5 //初始化Statement 17 6 statement = instantiateStatement(connection); 18 7 //设置查询超时时间 19 8 setStatementTimeout(statement); 20 9 setFetchSize(statement); 2110 return statement; 2211 } catch (SQLException e) { 2312 closeStatement(statement); 2413 throw e; 2514 } catch (Exception e) { 2615 closeStatement(statement); 2716 throw new ExecutorException("Error preparing statement. Cause: " + e, e); 2817 } 2918 } 30 31 1 protected Statement instantiateStatement(Connection connection) throws SQLException { 32 2 String sql = boundSql.getSql(); 33 3 if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { 34 4 String[] keyColumnNames = mappedStatement.getKeyColumns(); 35 5 if (keyColumnNames == null) { 36 6 return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); 37 7 } else { 38 8 return connection.prepareStatement(sql, keyColumnNames); 39 9 } 4010 } else if (mappedStatement.getResultSetType() != null) { 4111 return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); 4212 } else { 4313 //从数据库连接中获取statement 4414 return connection.prepareStatement(sql); 4515 } 4616 }

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

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


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

点赞
收藏

评论区

加载中...

相关推荐

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

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

java 复制Map对象(深拷贝与浅拷贝)

java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象