MyBatis源码3_运行分析_03_操作数据库

326 阅读10分钟

MyBatis运行

操作数据库

Executor

前面分析了MyBatis从mapper.xml到sql解析一直到生成Mapper的代理实例,了解了MyBatis整个工作流程。接下来我们来看MyBatis与数据库的交互。

先来看一段代码:

  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();
    }
  }

这是DefaultSqlSession中执行查询的源码,此处我们可以看到在执行查询的时候是通过executor这个对象去操作的,在DefaultSqlSession中所有与数据库的操作都是通过这个executor对象操作的。

同时在mybatis-config.xml配置文件中也可以指定执行sql默认所用的Executor。

<settings>
        <setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

因此我们大概可以知道Executor就是MyBatis针对数据库操作的一层封装,或者说是对JDBC的Statement的封装。 每个SqlSession对应一个Executor。

从配置我们可以看出来,Executor有多种类型,它们分别是

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

对应的的Executor实现类:

2023-05-04-22-00-42-image.png

Executor类型

Executor从配置上面来看,可以配置为SIMPLE, REUSE, BATC,分别对应了SimpleExecutor,ReuseExecutor,BatchExecutor

SimpleExecutor:简单执行器,也是MyBatis的默认执行器,每次执行一次数据操作(update,select)就开启一个Statement,用完就关闭。

ReuseExecutor:可重用的执行器,这里重用的是Statement,它会在一个Session作用域中,每次执行sql的时候会判断该sql是否已经执行过,每次执行过会将Statement进行缓存起来(一个Map),下次执行的时候直接重用上次的Statement。这种适用于开启一个会话要执行多次数据库操作,并且其中会带有重复的sql情况下使用。

BatchExecutor:批处理执行器,从名字就可以分辨出来,它会将多个SQL(一个会话),一次性执行。

以上三种Executor就是我们通常使用和可配置的Executor。

还有个特例独行的CachingExecutor。

CachingExecutor,自身不会有Executor的逻辑,它可以说是实际Executor的静态代理类,在原本的Executor基础上加了缓存功能,主要是针对查询,在执行sql前会查询一级缓存(Session级别的缓存)是否存在结果,如果存在则直接返回,否则就委派实际的Executor去执行,所委派的Executor就是上面的三种。

public class CachingExecutor implements Executor {

  private final Executor delegate; 
}

Executor的创建

Executor的创建是由SqlSessionFactory调用Configuration#newExecutor创建的。

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : 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);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

配置cacheEnabled默认为true,因此没有特殊配置的情况下都是开启了一级缓存了的,使用的是CachingExecutor,实际执行还是按照配置的ExecutorType创建的。

executor = (Executor) interceptorChain.pluginAll(executor); 拦截器,后面说。

StatementHandler

前面介绍了MyBatis中,真正与数据库交互的是通过Executor进行交互的,那么MyBatis是如何封装Executor,怎么用它操作数据库的呢?

接下来我们来看一段,Executor操作数据库的源码

org.apache.ibatis.executor.SimpleExecutor#doQuery

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();   
      // 创建StatementHandler,StatementHandler用于创建JDBC的Statement,以及执行查询和返回
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);  
      // 准备:java.sql.Statement,如对Statement设置参数等,设置超时参数等信息
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 最后执行查询,返回其结果
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

上面这一段是Executor查询的代码,update代码也类似:

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

通过上面代码分析,可以总结一下Executor操作数据库的步骤:

  1. 创建StatementHandler
  2. 用创建的StatementHandler,创建JDBC的java.sql.Statement,并设置参数
  3. 执行JDBC的Statement,并返回其结果

由上分析,可以看出StatementHandler是一个非常重要的类。

StatementHandler的作用:StatementHandler主要用于创建及设置JDBC中java.sql.Statement的参数和执行Statement,并封装返回结果。

如果忘记了JDBC的Statement,看一段这个原生的JDBC代码,就能知道它主要在干啥了:

    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
        //加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //建立连接
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "root", "root");
        //依据姓名查找学生信息
        String studentname="lili";
        String sql = "select * from student where studentname=?";
        //创建PrepareStatement
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, studentname);
        //执行SQL
        resultSet = preparedStatement.executeQuery();
        //处理结果
        while (resultSet.next()) {
            Student student = new Student();
            int id = resultSet.getInt("studentid");
            String name = resultSet.getString("studentname");
            student.setStudentID(id);
            student.setStudentName(name);
            System.out.println(student);
        }

    } catch (Exception e) {
        e.printStackTrace();
    //关闭资源
    } finally {

    }

上面JDBC代码中的PreparedStatement就是java.sql.Statement的实现类。

StatementHandler接口:

public interface StatementHandler {
  // 准备Statement,也就是创建Statement,并设置其超时参数
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  // 对Statement设置sql中的占位参数
  void parameterize(Statement statement)
      throws SQLException;
  // 批量操作
  void batch(Statement statement)
      throws SQLException;
  // update操作,删除也是它
  int update(Statement statement)
      throws SQLException;
  // 查询...
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  BoundSql getBoundSql();
  ParameterHandler getParameterHandler();

}

StatementHandler体系:

2023-05-05-19-51-02-image.png

它的继承体系跟Executor类似,真正干活儿的就是BaseStatementHandler的实现类,RoutingStatementHandler是一个静态代理类,内部跟CachingExecutor一样,有个delegate属性,是BaseStatementHandler三个实现类其中一个。

着重介绍一下,这个RoutingStatement的作用,在Executor体系中CachingExecutor是一个静态代理Executor在Executor实现的基础上增加了缓存功能。这里的RoutingStatement功能类似,但是它实现的不是缓存,它实现的是一个路由选择功能:

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

    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());
    }

  }

它会根据Statement类型,创建对应真正去操作这个Statement的Handler。

那么接下来你有可能要问了,这三种实现的的StatementHandler有什么区别呢?

  1. SimpleStatementHandler: 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句,也就是可以直接执行的sql语句。
  2. PreparedStatementHandler: 管理 Statement 对象并向数据中推送需要预编译的SQL语句,需要参数设置等操作的sql语句,是一个预编译的sql。
  3. CallableStatementHandler:管理 Statement 对象并调用数据库中的存储过程。

这个StatementType是在解析mapper.xml生成MappedStatement的时候就决定了。

StatementHandler源码

接下来我们分析一下StatementHandler的源码,从而推出另外两个重要组件ParameterHandler、ResultSetHandler。

先来看StatementHandler的创建:

 // org.apache.ibatis.executor.SimpleExecutor#doQuery
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建RoutingStatementHandler,RoutingStatement的创建逻辑在上面有介绍
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 拦截器,后面会介绍
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
 } 

可以看到,实际对外工作的跟Executor类似,是这个代理对象RoutingStatementHandler。

(StatementHandler) interceptorChain.pluginAll(statementHandler);这里又出现了类似创建Executor的代码,还是拦截器功能实现,后面会详细讲。

StatementHandler准备:

 // org.apache.ibatis.executor.SimpleExecutor#doQuery
   stmt = prepareStatement(handler, ms.getStatementLog());

   private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    // 创建Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 设置参数
    handler.parameterize(stmt);
    return stmt;
  }

这一步,创建了Statement,并且对其参数进行了设置

最后执行返回:

 // org.apache.ibatis.executor.SimpleExecutor#doQuery 
 return handler.query(stmt, resultHandler);

步骤很简单,就分了三步,现在我们来考虑一下这三步结合我们写原生JDBC风封装,比较难的点在哪儿?

首先,肯定不是创建一个Statement,毕竟JDBC提供了API一下就创建好了,主要难点在:参数设置和返回结果封装

那么MyBatis是如何做的呢?

在StatementHandler的功能实现类有个功能父类就是BaseStatementHandler,里面有两个重要属性:

2023-05-05-20-42-51-image.png

就是ResultSetHandler和ParameterHandler,一个对数据库操作结果进行封装成返回结果,一个是对数据库操作sql参数设置。

ParameterHandler

ParameterHandler没有上面的Executor和StatementHandler复杂

public interface ParameterHandler {
  // 获取参数对象  
  Object getParameterObject();

  // 针对PreparedStatement设置参数对象
  void setParameters(PreparedStatement ps) throws SQLException;

}

它的功能比较单一,就是给PreparedStatement设置参数,类似于JDBC的:

        //创建PrepareStatement
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, studentname);

并且ParameterHandler也只有一个实现类

2023-05-05-21-05-39-image.png

它的创建跟Executor和StatementHandler一样,都是由Configuration对象创建

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // .....
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    // ....
  }

  // org.apache.ibatis.session.Configuration#newParameterHandler
 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

mappedStatement.getLang():获取到的是LanguageDriver实例,在前面介绍解析MappedStatement的时候有说道对它的创建。

有注意到,parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);又是一段跟其它组件类似的,拦截器,照旧,后面会说。

另外着重说一下ParameterHandler的核心功能,设置参数

DefaultParameterHandler#setParameters

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {  
      // 遍历ParameterMapping,这个ParameterMapping集合的生成在前面也有说到
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {  
          // 解析参数值
          Object value;
          String propertyName = parameterMapping.getProperty();
          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有很多实现类
          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 | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

设置参数主要步骤:

  1. 遍历 BoundSql的parameterMappings,对每个parameter设置属性。并从传来的参数解析出值。解析BoundSql中的parameterMappings的源码在:SqlSourceBuilder#parse => 对ParameterMappingTokenHandler;
  2. 针对每个ParameterMapping获取到对应的TypeHandler
  3. 调用TypeHandler设置参数值

ResultSetHandler

前面分析了ParameterHandler,对sql参数设置,接下来我们分析对数据库操作结果进行封装,将会使用到ResultSetHandler。

public interface ResultSetHandler {
  // 处理结果集,用得最多的
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;
  // 批量处理结果集
  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
  // 处理存储过程结果
  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

2023-05-05-21-53-07-image.png

默认,也仅且一个实现类就是DefaultResultSetHandler。

跟其它的一样,创建ResultSetHandler也是由Configuration完成的

 protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // .....
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
    // ....
  }

  // org.apache.ibatis.session.Configuration#newResultSetHandler
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

创建逻辑比较简单,我们又看到了熟悉的身影 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); 拦截器,还是放到后面统一说。

接下来我们看它的核心方法:

  @Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;  
     // 获取第一个结果集,封装成ResultSetWrapper
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);  
    // 持续遍历结果,并处理:java.sql.ResultSet。将其封装到multipleResults集合中
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

在对返回结果封装入口代码就是:handleResultSet(rsw, resultMap, multipleResults, null);

ResultSetHandler是通过反射创建的返回对象,源码是DefaultResultSetHandler#createResultObject(....)。

MyBatis操作数据库总结

2023-05-07-10-28-27-image.png

上面这个图,描述了Executor在操作数据库的时候和JDBC操作数据库的对应关系步骤,我们可以看到Executor实际底层也是使用JDBC,只不过在操作Statement以及参数设置,查询返回等关键步骤封装成了组件,也就是:StatementHandler、ParameterHandler、ResultSetHandler,网上有把它们与Executor组件统称为”MyBatis四大组件“,“SqlSession四大对象“....。

Executor:负责发起操作数据库sql请求,初始化其它三大组件。

StatementHandler:语句处理器,负责和JDBC交互,包括创建JDBC的Statement,处理prepare、执行语句,调用ParameterHandler设置参数,调用ResultSetHandler封装返回结果。

ParameterHandler:参数处理器,负责处理预编译sql的入参,依赖ParameterMapping。

ResultSetHandler:结果处理器,负责将数据库操作结果映射成Java返回结果。

我们在追溯源码的时候,可以看到这四个组件,都在创建的时候,执行了一个类似于这样的语句:interceptorChain.pluginAll(resultSetHandler); 这是MyBatis提供的拦截器,方便编码人员能够随时干预数据库执行过程中的步骤,如参数改写,sql改写,返回结果再封装等操作,MyBatis并没有向Spring框架一样,提供回调接口的方式来进行扩展而是采用的拦截器,这种方式,扩展性更强一点,也更灵活,但是对新手也不太友好,因为需要了解到四大组件的功能具体API功能才能很好应用。

](blog.51cto.com/u_12393361/…)

MyBatis执行流程总结

至此就分析完了整个MyBatis运行阶段,从sql生成到sql执行的整体流程,接下来统一做个小总结

  1. 在启动的时候通过获取要扫描的mapper.xml或者注解,进行将sql片段解析为SqlNode对象,生成MappedStatement对象,放入Configuration中。
  2. 在运行过程中获取到Mapper接口实例,通过SqlSession#getMapper方法为入口,创建Mapper接口的代理实例,代理对象方法的执行者为MapperProxy。
  3. 调用Mapper接口实例的时候,实际执行逻辑是MapperProxy的invoke方法,该方法会通过启动时的MappedStatement以及执行过程中Mapper接口方法来进行判断当前执行方法是什么样的类型,从而调用SqlSession的方法,执行sql语句。
  4. SqlSession执行sql前,会通过第一步解析好的MappedStatement,从而根据参数解析出预编译sql,以及参数信息。
  5. 通过Executor执行sql语句。