欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-01-19 10:13:44
一,前言 我们在文章:Mybatis源码解析,一步一步从浅入深(二):按步骤解析源码的最后一步说到执行查询的关键代码: selelectOne方法有两个参数: 第一个参数是:com.zcz.learnmybatis.dao.UserDao.findUserById 第二个参数是:1(Integer类 ......

一,前言

  我们在文章: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