上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的。

准备工作

Mybatis完成一次SQL查询需要使用的代码如下:

Java代码  
  1. String resource = "mybatis.cfg.xml";
  2. Reader reader = Resources.getResourceAsReader(resource);
  3. SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
  4. <strong>  </strong>SqlSession session = ssf.openSession();
  5. try {
  6. UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");
  7. System.out.println(user);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. } finally {
  11. session.close();
  12. }

本次我们需要进行深入跟踪分析的是:

Java代码  
  1. SqlSession session = ssf.openSession();
  2. UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");

源码分析

第一步:打开一个会话,我们看看里面具体做了什么事情。

Java代码  
  1. SqlSession session = ssf.openSession();

DefaultSqlSessionFactory的 openSession()方法内容如下:

Java代码  
  1. public SqlSession openSession() {
  2. return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  3. }

跟进去,我们看一下openSessionFromDataSource方法到底做了啥:

Java代码  
  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Connection connection = null;
  3. try {
  4. final Environment environment = configuration.getEnvironment();
  5. final DataSource dataSource = getDataSourceFromEnvironment(environment);
  6. TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  7. connection = dataSource.getConnection();
  8. if (level != null) {
  9. connection.setTransactionIsolation(level.getLevel());
  10. }
  11. connection = wrapConnection(connection);
  12. Transaction tx = transactionFactory.newTransaction(connection, autoCommit);
  13. Executor executor = configuration.newExecutor(tx, execType);
  14. return new DefaultSqlSession(configuration, executor, autoCommit);
  15. } catch (Exception e) {
  16. closeConnection(connection);
  17. throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  18. } finally {
  19. ErrorContext.instance().reset();
  20. }
  21. }

这里我们分析一下这里所涉及的步骤:

(1)获取前面我们加载配置文件的环境信息,并且获取环境信息中配置的数据源。

(2)通过数据源获取一个连接,对连接进行包装代理(通过JDK的代理来实现日志功能)。

(3)设置连接的事务信息(是否自动提交、事务级别),从配置环境中获取事务工厂,事务工厂获取一个新的事务。

(4)传入事务对象获取一个新的执行器,并传入执行器、配置信息等获取一个执行会话对象。

从上面的代码我们可以得出,一次配置加载只能有且对应一个数据源。对于上述步骤,我们不难理解,我们重点看看新建执行器和DefaultSqlSession。

首先,我们看看newExecutor到底做了什么?

Java代码  
  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  2. executorType = executorType == null ? defaultExecutorType : executorType;
  3. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  4. Executor executor;
  5. if (ExecutorType.BATCH == executorType) {
  6. executor = new BatchExecutor(this, transaction);
  7. } else if (ExecutorType.REUSE == executorType) {
  8. executor = new ReuseExecutor(this, transaction);
  9. } else {
  10. executor = new SimpleExecutor(this, transaction);
  11. }
  12. if (cacheEnabled) {
  13. executor = new CachingExecutor(executor);
  14. }
  15. executor = (Executor) interceptorChain.pluginAll(executor);
  16. return executor;
  17. }

上面代码的执行步骤如下:

(1)判断执行器类型,如果配置文件中没有配置执行器类型,则采用默认执行类型ExecutorType.SIMPLE。

(2)根据执行器类型返回不同类型的执行器(执行器有三种,分别是 BatchExecutor、SimpleExecutor和CachingExecutor,后面我们再详细看看)。

(3)跟执行器绑定拦截器插件(这里也是使用代理来实现)。

DefaultSqlSession到底是干什么的呢?

DefaultSqlSession实现了SqlSession接口,里面有各种各样的SQL执行方法,主要用于SQL操作的对外接口,它会的调用执行器来执行实际的SQL语句。

接下来我们看看SQL查询是怎么进行的

Java代码  
  1. UserInfo user = (UserInfo) session.selectOne("User.selectUser", "1");

实际调用的是DefaultSqlSession类的selectOne方法,该方法代码如下:

Java代码  
  1. public Object selectOne(String statement, Object parameter) {
  2. // Popular vote was to return null on 0 results and throw exception on too many.
  3. List list = selectList(statement, parameter);
  4. if (list.size() == 1) {
  5. return list.get(0);
  6. } else if (list.size() > 1) {
  7. throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  8. } else {
  9. return null;
  10. }
  11. }

我们再看看selectList方法(实际上是调用该类的另一个selectList方法来实现的):

Java代码  
  1. public List selectList(String statement, Object parameter) {
  2. return selectList(statement, parameter, RowBounds.DEFAULT);
  3. }
  4. public List selectList(String statement, Object parameter, RowBounds rowBounds) {
  5. try {
  6. MappedStatement ms = configuration.getMappedStatement(statement);
  7. return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  8. } catch (Exception e) {
  9. throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  10. } finally {
  11. ErrorContext.instance().reset();
  12. }
  13. }

第二个selectList的执行步骤如下:

(1)根据SQL的ID到配置信息中找对应的MappedStatement,在之前配置被加载初始化的时候我们看到了系统会把配置文件中的SQL块解析并放到一个MappedStatement里面,并将MappedStatement对象放到一个Map里面进行存放,Map的key值是该SQL块的ID。

(2)调用执行器的query方法,传入MappedStatement对象、SQL参数对象、范围对象(此处为空)和结果处理方式。

好了,目前只剩下一个疑问,那就是执行器到底怎么执行SQL的呢?

上面我们知道了,默认情况下是采用SimpleExecutor执行的,我们看看这个类的doQuery方法:

Java代码  
  1. public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  2. Statement stmt = null;
  3. try {
  4. Configuration configuration = ms.getConfiguration();
  5. StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler);
  6. stmt = prepareStatement(handler);
  7. return handler.query(stmt, resultHandler);
  8. } finally {
  9. closeStatement(stmt);
  10. }
  11. }

doQuery方法的内部执行步骤:

(1) 获取配置信息对象。

(2)通过配置对象获取一个新的StatementHandler,该类主要用来处理一次SQL操作。

(3)预处理StatementHandler对象,得到Statement对象。

(4)传入Statement和结果处理对象,通过StatementHandler的query方法来执行SQL,并对执行结果进行处理。

我们看一下newStatementHandler到底做了什么?

Java代码  
  1. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {
  2. StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);
  3. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  4. return statementHandler;
  5. }

上面代码的执行步骤:

(1)根据相关的参数获取对应的StatementHandler对象。

(2)为StatementHandler对象绑定拦截器插件。

RoutingStatementHandler类的构造方法RoutingStatementHandler如下:

Java代码  
  1. public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) {
  2. switch (ms.getStatementType()) {
  3. case STATEMENT:
  4. delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
  5. break;
  6. case PREPARED:
  7. delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
  8. break;
  9. case CALLABLE:
  10. delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler);
  11. break;
  12. default:
  13. throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
  14. }
  15. }

根据 MappedStatement对象的StatementType来创建不同的StatementHandler,这个跟前面执行器的方式类似。StatementType有STATEMENT、PREPARED和CALLABLE三种类型,跟JDBC里面的Statement类型一一对应。

我们看一下prepareStatement方法具体内容:

Java代码  
  1. private Statement prepareStatement(StatementHandler handler) throws SQLException {
  2. Statement stmt;
  3. Connection connection = transaction.getConnection();
  4. //从连接中获取Statement对象
  5. stmt = handler.prepare(connection);
  6. //处理预编译的传入参数
  7. handler.parameterize(stmt);
  8. return stmt;
  9. }
来源: http://chenjc-it.iteye.com/blog/1541947
null

转载于:https://www.cnblogs.com/jeffen/p/6251197.html

MyBatis原理分析之四:一次SQL查询的源码分析相关推荐

  1. 原理分析之四:一次SQL查询的源码分析

    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: Java代码   String res ...

  2. Android 系统(177)---Android消息机制分析:Handler、Looper、MessageQueue源码分析

    Android消息机制分析:Handler.Looper.MessageQueue源码分析 1.前言 关于Handler消息机制的博客实际上是非常多的了. 之前也是看别人的博客过来的,但是过了一段时间 ...

  3. 【SA8295P 源码分析】18 - Camera Bringup 流程 及 源码分析

    [SA8295P 源码分析]18 - Camera Bringup 流程 及 源码分析 一.Camera Bringup 流程 1.1 CameraConfigSA8295.c 配置文件解析 1.2 ...

  4. mybatis第十话 - mybaits整个事务流程的源码分析

    1.故事前因 在分析mybatis源码时一直带的疑问,一直以为事务是在SqlSessionTemplate#SqlSessionInterceptor#invoke完成的,直到断点才发现并不简单! 在 ...

  5. 【源码分析】极验验证官方SDK源码分析和实现思路

    前言 2016年就这么来了,新的一年,继续努力~ 最近,除了12306的验证码火起来以后,还有一个在界面上拖拽的验证码,也火了起来,就是这次要说的极验验证,在这个万众创新的时代,工具类产品能做到这样, ...

  6. 【Android 插件化】VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )

    文章目录 一.目前的 API 现状 二.安装应用源码分析 1.安装按钮执行的操作 2.返回到 HomeActivity 执行的操作 一.目前的 API 现状 下图是 VirtualApp 官方给出的集 ...

  7. android gps源码分析,Android编程之Android GPS ——AGPS源码分析及配置

    本文主要介绍了Android编程的Android GPS --AGPS源码分析及配置,通过具体的分析以及源码,向大家展示了这些,希望对大家学习Android编程有所帮助. 1:冷启动指令: locat ...

  8. MyBatis骨骼惊奇,跟着腾讯大牛学源码分析,总结出这份pdf文档

    什么是MyBatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为 ...

  9. redis zset转set 反序列化失败_Redis只往zset有序集合添加不存在的数据:关键字索引查询构建+源码分析...

    Redis的有序集合Sorted Set(zset),可以很方便地用来构建关键字索引表,可以很方便地实现支持超大规模并发的关键字组合条件查询. 比如有套博客系统,博客文章存放在 hash 类型 art ...

最新文章

  1. 清晰易懂的Numpy进阶教程
  2. 缓存DNS域名解析服务器的制作方法
  3. C++的黑科技 利用一个字符对字符串进行分离
  4. HTML的SEO(搜索引擎优化)标准
  5. Lubuntu 18.10仍有可能支持32位PC
  6. ac ap原理、_AP面板是什么?家庭AC+AP的组网方式,真的适合所有人吗?
  7. python方法大全参数是对象_向对象方法Python传递太多参数
  8. pytorch nn.Module.zero_grad
  9. Flink Remote Shuffle 开源:面向流批一体与云原生的 Shuffle 服务
  10. python读取文件夹下所有图像_Python 读取指定文件夹下的所有图像方法
  11. 【Nodejs篇三】Node js npm包管理工具
  12. Math详解大全,数学类
  13. 偏微分方程数值解法pdf_数值模拟偏微分方程的三种方法:FDM、FEM及FVM
  14. 关于Demo3D中的Random
  15. Pycharm CPU占用100%
  16. java socket发送json_Java中使用Socket发送Java对象实例
  17. 笔记本电脑微信视频对方却听不到声音
  18. PHP合成生成GIF动图
  19. 这15个PDF转化工具
  20. 保险行业的电子签章应用场景:印章统一管、合同在线签

热门文章

  1. Win10系统如何在防火墙里开放端口
  2. 【C#】数组的最大最小值
  3. 【VB】学生信息管理系统1——系统设计怎样开始?
  4. shap_value
  5. Python3 reversed 函数
  6. LeetCode简单题之设计停车系统
  7. Harmony生命周期
  8. 客快物流大数据项目(五十四):初始化Spark流式计算程序
  9. 2021年大数据常用语言Scala(七):基础语法学习 条件表达式
  10. 2021年大数据Flink(十七):Flink四大基石