mybatis原理解析---SqlSession运行过程(下)

继续sqlSession运行过程分析,上一篇文章讲到通过sqlSession中executor对象的query方法执行的查询,在分析这个方法的源码之前,先分析下SqlSession执行sql中用到的比较重要的几个对象。SqlSession中是通过Executor 、StatementHandler、ParameterHandler、ResultHandler来完成数据库操作和结果返回的。先稍微来了解下这几个对象大致的作用:

  • Executor:代表执行器,由其来调度StatementHandler、ParameterHandler、ResultHandler来执行对应的sql并返回结果。
  • StatementHandler:使用数据库的Statement对象(PrepareStatement)完成实际的查询,是四大对象的核心,起到承上启下的作用。
  • ParameterHandler:用来sql对参数的处理。
  • ResultHandler:对查询返回的结果集(ResultSet)进行封装处理后返回。

下面详细来研究上面提到的四个对象。

1.执行器(Executor)

执行器起(Executor)了至关重要的作用。它是一个真正执行Java和数据库交互的组件,在Mybatis中存在三种类型的执行器,可以在setting属性中配置:

  • SIMPLE,简单执行器,不配置它就是默认的执行器。
  • REUSE,一种重用预处理语句的执行器。
  • BATCH,执行会重用预处理语句和支持批量更新,它是针对批量操作专用的执行器。
    他们都提供了查询和更新相关的方法以及相关事务的方法,下面是Executor接口中的部分方法:
 int update(MappedStatement ms, Object parameter) throws SQLException;

    List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

    List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

    Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

   List flushStatements() throws SQLException;

   void commit(boolean required) throws SQLException;

   void rollback(boolean required) throws SQLException;

下面先不看这些方法的具体实现,而是先来了解下如何来创建Executor,首先sqlSession中关联的Executor对象是在调用SqlSessionFactory.openSession()的时候创建的,具体的代码如下:

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);
      //execType是执行器的类型,在openSession()方法中可以传入; tx是指定的事务类型
      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();
    }
  }

可以看到Executor对象是通过configuration.newExecutor()方法创建的,传入的两个参数是事务类型和传入的执行器类型;下面来看下这个方法的具体实现:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    //如果配置了二级缓存,则会创建一个CachingExecutor对当前Executor进行一层包装,这个对象负责执行缓存逻辑
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

这里defaultExecutorType就是我们在setting属性中配置的执行器类型,这里需要注意下,从上面的代码逻辑中可以看到如果我们配置了defaultExecutorType,但在openSession()的时候又指定了Executor的类型,那么setting中的配置是不会生效的。
这段代码的逻辑其实很简单,就是根据不同的ExecutorType来创建不同类型的Executor对象,如果在配置中启用了二级缓存,则这里会对创建出的executor对象进行一层包装,得到一个CachingExecutor对象,由其来负责执行缓存逻辑。在返回executor对象之前,还有一句interceptorChain.pluginAll(executor)这样的代码,这其实是mybatis的插件,这个方法里将会构建层层的动态代理对象,这样在执行真实的executor之前会先执行插件的代码。
现在来看下具体查询的实现,下面是SimpleExecutor对象的doQuery()方法:

Override
  public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {//最后需要关闭Statement对象,防止资源泄露
      closeStatement(stmt);
    }
  }

可以看到里面先通过configuration.newStatementHandler()来创建了一个StatementHandler对象,然后将其作为参数调用prepareStatement()得到了Statement对象,最后将Statement对象和处理结果的resultHandler对象作为参数传给其query()方法实现sql的执行和结果集的包装。
下面是prepareStatement方法的实现:

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //从transaction中取到数据
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

是通过StatementHandler的prepare()方法拿到预编译的statement对象,然后再通过其parameterize()来为预编译好的sql设置参数。
通过上面的分析可以看到sql的预编译、参数设置、sql执行、返回结果的处理都是通过StatementHandler进行处理的,Executor只是起到了一个调度作用(实际上Executor中还是做了很多事情的,比如说二级缓存的处理)。

2.数据库会话器(StatementHandler)

数据库会话器顾名思义就是用来处理数据库会话的,从上面已经可以看到StatementHandler的重要作用了。先来看下Configuration是如何创建statementHandler对象的,下面是configuration.newStatementHandler()的源码:

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

可以看到真实创建的是一个RoutingStatementHandler对象,这不是一个真实提供服务的对象,而是通过适配模式找到对应的StatementHandler来执行。在mybatis中StatementHandler和Executor一样也分三种:SimpleStatementHandler PreparedStatementHandler CallableStatementHandler,分别对应前面说的三种类型的执行器。
在初始化RoutingStatementHandler对象的时候,会根据上下文环境创建哪个StatementHandler对象,我们看下RoutingStatementHandler的源码。

public class RoutingStatementHandler implements StatementHandler {

  //delegate是真正提供服务的SimpleStatementHandler对象
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    //根据MappedStatement中不同的StatementType类型,构造不同类型的代理对象.
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

 //所有方法都是转发到代理对象delegate上去
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    delegate.batch(statement);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
  }

  @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

  @Override
  public  Cursor queryCursor(Statement statement) throws SQLException {
    return delegate.queryCursor(statement);
  }

  @Override
  public BoundSql getBoundSql() {
    return delegate.getBoundSql();
  }

  @Override
  public ParameterHandler getParameterHandler() {
    return delegate.getParameterHandler();
  }
}

可以看到RoutingStatementHandler完全是一个代理对象,内部的所有实际操作比如update query等都是直接转发给内部代理对象delegate的相关方法。
delegate的具体的值是在RoutingStatementHandler的构造器中根据传入MappedStatement对象中的StatementType来构造的。

下面以最常用的PrepareStatementHandler为例来分析下sql的执行过程,前面介绍执行器的时候重点用到了prepare()、parameterize()和query()方法,下面就从这三个方法的源码入手。
先来看下prepare()方法的实现,这个方法是在其父类BaseStatementHandler,prepare()中实现的:

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //从Connection中获取statement对象
      statement = instantiateStatement(connection);
      //设置statement的超时时间,这里的超时时间可能有多个配置,优先级是:
      //事务的超时时间设置>mapper.xml中单条sql语句超时时间配置>mybatis-config.xml中配置的超时时间
      //如果事务的超时时间大于后两种,则仍会选择后两种中优先级高的那个.
      setStatementTimeout(statement, transactionTimeout);
      //设置可获取的最大行数
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

获取statement对象是在instantiateStatement()中实现的,而instantiateStatement()在BaseStatementHandler是个抽象方法,需要各个子类中进行具体实现。下面是PrepareStatementHandler中对这个方法的实现:

@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    //如果设置了主键(keyGenerator),那么则需要调用connection的对应prepareStatement方法
    //获取支持回填主键的PrepareStatement对象
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) { //只设置了keyGenerator没有指定具体主键,则使用主键
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
     //设置PrepareStatement的resultSetType属性
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
     //没有设置,获取默认情况的prepareStatement
      return connection.prepareStatement(sql);
    }
  }

总的来说PrepareStatementHandler的prepare()方法完成了对sql的预编译,针对mappedStatement中不同的设置(是否设置了主键 返回值ResultSet的类型等),获得不同的PrepareStatement对象,然后给这个对象进行超时设置和可以获取最大结果行数的设定。
下面看下parameterize()方法,这个方法负责完成对方法参数的设置:

@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

这个方法中是借助的ParameterHandler的setParameters方法来对参数进行的设定,入参是PreparedStatement类型的对象。后面会对ParameterHandler进行介绍,所以这里暂时跳过。

最后看下一个具体的查询方法:PrepareStatementHandler的query()方法:

@Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
  }

可以看到逻辑非常简单,这是因为前面已经获得了预编译的PreparedStatement参数也已经设置完成,所以执行的时候只需要简单的调用PreparedStatement.execute()就行了.执行完成之后,就通过ResultSetHandler的handleResultSets()方法来对返回结果进行包装。

分析完StatementHandler的源码之后,一条查询sql的执行流程就很清楚了:
Executor负责将传入的参数转换为parameterObject对象,然后调用StatementHandler的prepare()获取Statement对象并设置一些参数(超时时间等),
然后通过parameterize()方法为statement设置参数,这一步实际上是通过ParameterHandler实现的,然后就是执行查询这里就是直接调用statement的
execute()方法来执行,最后通过resultSetHandler对查询返回的结果进行包装.update等操作流程也是类似的。

3.参数处理器(ParameterHandler)

可以看到在StatementHandler中是通过ParameterHandler的setParameters给预编译好的sql设置参数的.其实整个ParameterHandler的作用就是完成参数的设置。ParameterHandler是一个接口,下面看下其定义:

public interface ParameterHandler {

  Object getParameterObject();

  void setParameters(PreparedStatement ps)
      throws SQLException;

}

主要就是两个方法,getParameter用来获取传入的参数和setParameters给传入的PrepareStatement对象设置参数。mybatis提供的默认实现是DefaultParameterHandler,下面主要看下其setParameters的实现:

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    //parameterMappings上参数名称是按设置顺序存储的,所以只需要依次拉取出这些参数名称对应的参数值设置到PreparedStatement指定位置上去就行了
    List parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();//sql中以#{}形式设置的参数名,下面主要是确定这个参数对应的值
          //依次从BoundSql对象中的additionalParameter parameterObject metaObject中取值.

          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          //使用typeHandler对参数进行处理,这里的typeHandler可以是mapper.xml中配置的.如果进行配置,
          //则会使用mybatis默认提供的typeHandler进行处理
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

4.结果处理器(ResultSetHandler)

ResultSetHandler的主要作用就是对查询之后的结果进行处理;前面在StatementHandler中通过PrepareStatement的execute()方法执行完查询之后,就调用ResultSetHandler的handleResultSets()方法来处理的结果。下面是这个接口的定义:

public interface ResultSetHandler {

   List handleResultSets(Statement stmt) throws SQLException;

   Cursor handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

前两个方法都是用来对结果集进行处理的,handleOutputParameters()是用来处理存储过程输入输出参数的。mybatis为这个接口提供的默认实现类是DefaultResultSetHandler。这些方法的实现都比较繁琐,先是使用JAVASSIST或者CGLIB进行延迟加载,然后使用TypeHandler和ObjectFactory进行结果组装返回,这里就不再展说了,有兴趣的可以自己去看下源码。

经过上面四个对象的介绍,其实整个SqlSession运行的原理应该已经非常清楚了,下面再通过一张图来回顾下SqlSession执行一次查询和更新的逻辑:

mybatis原理解析---SqlSession运行过程(下)_第1张图片

SqlSession是通过Executor创建StatementHandler来运行的,而StatementHandler要经过下面三步:

  • prepare预编译sql得到PrepareStatement对象
  • parameterize给PrepareStatement对象设置参数
  • 调用PrepareStatement对象的execute()执行query/update
    其中parameterize是调用ParameterHandler的方法去设置的,而参数是根据类型处理typeHandler去处理的。query/update操作执行完成之后,就通过ResultHandler进行结果集的封装,如果是update语句它就返回整数,否则它就通过typeHandler处理结果类型,然后用ObjectFactory提供的规则组装对象,返回给调用者。

你可能感兴趣的:(深入学习mybatis)