IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> mybatis源码(四) -> 正文阅读

[大数据]mybatis源码(四)

上篇文章解决了第三个问题,即讲解了xml中sql命令与接口中方法的绑定过程。本篇文章继续追踪源码,尝试答疑最后一个问题,在开始正式追踪源码,再次确认下我们要研究的问题:
????????1、mybatis如何找到存储sql的xml文件
? ? ? ? 2、mybatis如何解析xml中sql相关标签并拼装成完整的sql语句
? ? ? ? 3、mybatis如何将xml中sql与接口中方法绑定
? ? ? ? 4、mybatis如何将sql发送出去并获得结果

接着上篇文章讲,下面我们来看看MapperMethod执行的源码:

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //根据sql命令类型跳到对应的处理方法(我们demo中是select类型,所以下面直接讲解select的处理,其它的处理不深究)
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        // 根据不同的返回类型跳转到不同的执行方法
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          //将方法中的参数转换成sql命令中的参数(demo中未定义上述的各种返回类型,所以会执行到当前代码行)
          Object param = method.convertArgsToSqlCommandParam(args);
          //如果不是上述描述的多返回类型,那么默认查询结果是单个返回值,所以走到selectOne中
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

select查询时会根据不同的返回类型执行不同的方法,因为我们demo查询的是单个返回对象所以最终会执行sqlSession.selectOne方法,下面我们接着看看其实现:

?selectOne是一个接口,有三个实现类,因为我们的sqlsession会话是defaultSqlSession,所以直接进入第一个实现类中查看:

?可以看到selectOne方法其实也是返回了一个结果集合,然后选取了第一个对象返回

?这里可以看到selectList最后加了一个分页参数RowBounds.Default,即list默认查询第一页,并且最大返回数为整型最大值。再接着向下看:

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据唯一标识“包名.类名.方法名”从配置文件获取sql相关信息
      MappedStatement ms = configuration.getMappedStatement(statement);
      //执行器执行查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

executor.query是一个接口方法,实现类有两个,但是我们当前的实现是CacheExecutor,这个在最终源码时没有留意,不过可以从debug中看出来,如下:

?这从侧面也可以反映出mybatis的一个特性:session级别的查询默认是走缓存的。我们再接着向下看:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //根据xml中的sql信息生成BoundSql对象,其包含了基本是最终版本的sql语句(这里之所以还不是最终sql语句是因为参数还没替换到sql中,此时参数放置的位置是用 ? 代替的)和参数信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //根据当前sql信息,查询的分页信息创建唯一key。后续相同key的查询可以直接获取结果
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //进行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

这里BoundSql的生成过程不是我们追寻的重点,所以这里直接跳过,下面直接看最重要的query方法:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //获取当前查询对应的缓存容器
    Cache cache = ms.getCache();
    //如果缓存存在则刷新缓存
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //从缓存中获取结果,如果获取不到再进入到数据库查询阶段
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //如果没有缓存,则直接进入数据库查询阶段
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

query方法中会先查缓存,如果没有开启缓存或者缓存中没有对应的信息,则进入数据库查询阶段,其它的都不是我们此次关注的重点,所以我们这里直接进入delegate.query方法。

delegate.query也是一个接口方法,该方法有两个实现类BaseExecutor和CachingExecutor,因为当前我们就在后者类的query方法中,所以推测下一步会进入BaseExecutor类中。当然如果不确定也可以在所有实现类的对应方法中加断点。

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    //初始化一些信息
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //查看缓存中是否有当前查询的信息,如果有在进入handleLocallyCachedOutputParameters,如果没有则进入数据库查询阶段。数据库查询是我们此行的重点,所以这里直接进入queryFromDatabase方法中一探究竟
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //将当前key放入本地缓存
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //进入查询
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

这里doQuery方法是具体的查询方法,所以这里直接进入该方法:

?还记得前面文章我们跟踪创建的SimpleExecutor对象吗,这里我们进入这个实现类中:

?可以看到最终通过statement预处理查询,这是jdbc接口中执行查询相关的接口,所以最终mybatis仍是通过jdbc进行的查询,至于具体如何传输用户密码以及建立连接和结果解析,这里不再细讲,有兴趣的可以继续追踪。至此我们四个问题已经追踪结束,对于mybatis整体的操作流程我们有了个了解,但是里面很多的细节如xml到底如何解析,参数如何转换,结果如何映射成对象,甚至是最后一问中如何建立的数据库连接都没有进行深入探究。因为时间原因,这些再本次文章中没有体现,如果以后有机会再展现给大家吧。

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-12-26 22:16:11  更:2021-12-26 22:16:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:37:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码