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

367 阅读3分钟

前言

接上章,容器启动之后,给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);
    }
  }