水煮MyBatis(四)- 方法执行过程

前言

接上章,容器启动之后,给mapper接口中的方法生成了statement,就是为了下一步执行的时候,直接取用,这一章就详细聊聊这块的源代码。与标准的SQL语句不同,mybatis里参数的赋值是通过#或者$包裹的变量名称。

解析案例

    @Autowired
    private ImageInfoMapper imageInfoMapper;

    @Test
    public void select() {
        ImageInfo info = imageInfoMapper.byMd5("6e705a7733ac5gbwopmp02");
        log.info(">>>>>>>>>>>>>>>>>>>=>,{}", info);
    }

序列图

MapperMethod对应的是Mapper里的一个方法,把方法名和命令类型的信息保存到容器上下文。
image.png

执行过程

几个主要的类,在上面的序列图中有展示,这里就按照这个流程,捡几个关键点聊一下。

execute方法

因为上面的例子是查询单个结果,所以这里忽略其他的命令类型,只展示selectOne方法。源代码比较简单,是根据命令类型

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT:  // 忽略展示
      case UPDATE:// 忽略展示
      case DELETE: // 忽略展示
      case SELECT:
        if (...)
         // 忽略展示其他查询场景,比如查询多个结果,executeForMany(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          // 查询单个结果
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    return result;
  }

其中MapperMethod是启动注册Mapper时,给方法生成的一个对象,数据如下:
image.png

执行拦截器

可以说,这就是方法主体了,主要有三个步骤:

  • 生成sqlSession;
  • 执行方法;
  • 关闭sqlSession;
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 生成sqlSession
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 执行具体方法
        return method.invoke(sqlSession, args);
      } catch (Throwable t) {
        ...
      } finally {
        if (sqlSession != null) {
          // 关闭sqlSession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }

获取session

从一系列代码中,可以看出session中包含了事务信息、执行器、运行环境信息、是否自动提交等关键数据。其中Executor有多种类型,批量执行器、可重用执行器、默认执行器,这里获取的就是默认执行器。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    // 事务信息
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 最终指向执行上下文
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

创建sqlsession经过了以下几个主要步骤:

  • 从配置中获取Environment;
  • 从Environment中取得DataSource;
  • 从Environment中取得TransactionFactory;
  • 从DataSource里获取数据库连接对象Connection;
  • 在取得的数据库连接上创建事务对象Transaction;
  • 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
  • 创建sqlsession对象。

查询逻辑

这里也比较简单,查询列表数据,从启动时注册好的上下文环境中【前一章介绍的】,取出对应的MappedStatement,下个段落的BoundSql对象,也是从这里取出的。

    private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

获取绑定的sql

从configuration对象中获取boundSql对象【StaticSqlSource】,也是在解析Mapper时就已经处理好的方法信息。

  // 从MappedStatement中获取
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  
  public BoundSql getBoundSql(Object parameterObject) {
    // 从当前对象中组织属性信息,返回BoundSql对象
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

image.png

执行细节

这里已经到了底层执行细节,可以看出和jdbc里执行PrepareStatement对象顺序是一致的

  1. 用sql作为参数,创建PreparedStatement对象;
  2. 给sql中的参数占位符设置数值;
  3. 执行查询;
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 获取mybatis配置信息,主要是获取其中的datasource对象
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 设置参数
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 这里的query方法主要是 stmt.execute(),和JDBC一致
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
全部评论

相关推荐

抱抱碍事梨a:三点建议,第一点是建议再做一个项目,把自我介绍部分顶了,第二点是中南大学加黑加粗,第三点是建议加v详细交流
点赞 评论 收藏
分享
咦哟,从去年八月份开始长跑,两处实习转正都失败了,风雨飘摇,终于拿到offer了更新一下面试记录:秋招:多部门反复面试然后挂掉然后复活,具体问了啥已经忘了,只是被反复煎炸,直至焦香😋春招:base北京抖音hr打来电话说再次复活,准备面试,gogogo北京抖音一面:六道笔试题:1.promise顺序2.定义域问题3.flat展开4.并发请求5.岛屿数量算法(力扣)深度,广度都写6.忘记了,好像也是算法,难度中等其他问题多是框架底层设计,实习项目重难点~~~秒过😇北京抖音二面:三道笔试题:(为什么只有三道是因为第三道没做出来,卡住了)1.中等难度算法(忘记啥题了,应该是个数组的)2.认识js的继承本质(手写继承模式,深入js的面相对象开发)3.手写vue的响应式(卡在了watch,导致挂掉)---后知后觉是我的注册副作用函数写得有问题,有点紧张了其他题目多是项目拷打,项目亮点,对实习项目的贡献~~~第二天,挂,but立马复活转战深圳客服当天约面深圳客服一面:六道笔试题,由于面过太多次字节,面试官叫我直接写,不用讲,快些写完😋,具体都是些继承,深拷贝(注意对数组对象分开处理,深层次对象,循环引用),加中等难度算法题~~~秒过深圳客服二面:口诉八股大战:大概囊括网络,浏览器渲染原理,动画优化,时间循环,任务队列等等(你能想到的简单八股通通拉出来鞭尸😋)算法题:笔试题6道:1:找出数组内重复的数,arr[0]-arr[n]内的数大小为[1-n],例如[1,2,2,3,3]返回[2,3],要求o(n),且不使用任何额外空间(做到了o(n),空间方面欠佳,给面试官说进入下一题,做不来了)2:原滋原味的继承(所以继承真滴很重要)3:力扣股票购买时机难度中等其他滴也忘记了,因为拿到offer后鼠鼠一下子就落地了,脑子自动过滤掉可能会攻击鼠鼠的记忆😷~~~秒过深圳客服三面:项目大战参与战斗的人员有:成员1:表单封装及其底层原理,使用成本的优化,声明式表单成员2:公司内部库生命周期管理成员3:第三方库和内部库冲突如何源码断点调试并打补丁解决成员4:埋点的艺术成员5:线上项目捷报频传如何查出内鬼成员6:大文件分片的风流趣事成员7:设计模式对对碰成员8:我构建hooks应对经理的新增的小需求的故事可能项目回答的比较流利,笔试题3道,都很简单,相信大家应该都可以手拿把掐😇~~~过过过无hr面后续煎熬等待几天直接hr打电话发offer了,希望大家也可以拿到自己心仪的offer
法力无边年:牛哇,你真是准备得充分,我对你没有嫉妒,都是实打实付出
查看19道真题和解析
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务