上回我们讲到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

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

  1. MyBatis原理分析之四:一次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. 【源码分析】极验验证官方SDK源码分析和实现思路

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

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

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

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

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

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

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

  8. 【并发编程系列6】Condition队列原理及await和singal(等待/唤醒)机制源码分析

    Condition队列原理分析 前言 初识Condition Condition使用示例 Condition原理分析 condition.wait()源码解读 AQS#await() AQS#addC ...

  9. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

最新文章

  1. JavaSript模块化 AMD CMD 详解.....
  2. 模拟RAID-10其中一个硬盘损坏
  3. linux查看java进程cpu占用过高
  4. 牛客网_PAT乙级1014_科学计数法 (20)
  5. 在U盘上安装linux
  6. SQL 语句 - Select(1): 指定表
  7. js 的正则表达式 部分展示test()方法的验证功能
  8. 95-34-035-Context-HeadContext和TailContext
  9. linux awk 管道,shell使用管道,配合awk,一句代码清空大于设定大小的日志文件
  10. Ubuntu 更改源
  11. [PTA C语言]冒泡法排序
  12. 数据运营平台-数据采集
  13. 搜索系统硬盘中包含指定字符串的文件的工具和方法——全文搜索、搜索文件内容(持续更新中)
  14. 第五章-对单词进行分类和标记
  15. 信息化与信息化系统__无线__网络工程标准
  16. Android游戏开发---碰撞检测
  17. 人类一败涂地做图教程_人类一败涂地-怎么制作地图-地图制作教程详细入门级...
  18. UDS诊断系列介绍13-31服务
  19. 用半年的时间面试自己
  20. 基于sklearn实现LDA主题模型(附实战案例)

热门文章

  1. 重写 button 的创建方法
  2. 并发和在线用户数的思考
  3. 关于C#(ASP.net)存取MySQL LongText字段的心得[转]
  4. oracle数据库函数和存储过程的包
  5. 删除计算机系学生的选课记录6,天津理工大学+数据库实验二.doc
  6. 重庆python培训-重庆Python培训学校
  7. python使用方法-Python的使用方法
  8. python视频课程推荐-听说程序员都在用,5款Python开发工具推荐
  9. python爬虫抓取数据的步骤-Python爬虫抓取手机APP的传输数据
  10. 网页版python叫什么-我不想浏览网页,那么我就用Python实现网页自动化朗读!