mybatis源码解读(八):Statement 语句执行详解

功能

mybatis执行语句的操作是由StatementHandler完成的,它会去获取连接并且根据你配置的一些参数来准备好连接数据库的statment语句,然后通过StatementHandler执行语句并且将结果根据你配置的ResultMap封装成为你想要的实体类。
StatementHandler完成的操作:

  • 获取连接,根据配置准备数据库可执行的statement语句。
  • 执行语句
  • 通过ResultSetHandler根据resultMapping封装成为你想要的的实体对象

UML

在这里插入图片描述
StatementHandler的子类主要有三个,这三个是根据Configuration配置的StatementType策略去分别生成的

  • SimpleStatementHandler StatementType.STATEMENT 普通的statment,有sql注入的风险
  • PreparedStatementHandler StatementType.PREPARED 预处理statment
  • CallableStatementHandler StatementType.CALLABLE 可使用执行器

实现StatementHandler的还有BaseStatementHandler,这是一个基础类,这个类中包含了一些公用的功能
RoutingStatementHandler 只是一个路由,它内部有一个策略然后去执行不同的statment

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

  }

代码解析

构建 StatementHandler 是在 configuration.newStatementHandler()这个方法中完成的,最终会生成一个RoutingStatementHandler 代理类,delegate 是具体的StatementHandler ,会根据MappedStatement配置的策略去生成,方法最后会将handler放到插件中去拦截

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

prepareStatement()方法中,逻辑也很清晰,第一步是获取连接,第二步是生成statment语句,第三步是将入参放入到生成的statement语句中。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

getConnection()方法中,其实是调用了JdbcTransaction的getConnection()方法,而在这个方法中又调用了openConnection(),在openConnection()中会去调用java的API 原生datasource去获取连接,在连接成功后就是设置事务的隔离级别以及是否自动提交事务等属性。

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
  }
  
  // JdbcTransaction
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }
   // JdbcTransaction
   protected void openConnection() throws SQLException {
    // 原生api
    connection = dataSource.getConnection();
    // 隔离级别
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    // 事务是否自动提交
    setDesiredAutoCommit(autoCommit);
  }

handler.prepare()方法完成的功能就是初始化statment语句

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
      /.../
      // 初始化
      statement = instantiateStatement(connection);
      // 设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 设置每次获取的大小
      setFetchSize(statement);
      return statement;
      /.../
  }

instantiateStatement()方法中,会根据你的配置去设置一些statment语句配置,比如如果你配置了keyGenerate,他会去根据你配置的要自动生成的数据库列名来为你自动生成值并且设置主键值,然后就是如果你设置了ResultSetType类型,则会根据你的类型去生成statement,这些都是java原生操作。

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    // 是否配置了主键生成策略
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    // 是否配置了resultSetType
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

设置超时时间,这里的超时时间有个生效的优先级,首先是在事务管理器中配置的超时时间,然后是在mapper中配置的超时时间,最后才是configuration中配置的超时时间,可以从逻辑中看到,最终会有个 transactionTimeout < queryTimeout 这样的比较,这是为了保证事务中设置的超时时间优先。

protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
    Integer queryTimeout = null;
    // mapper中设置的超时时间为第二优先级
    if (mappedStatement.getTimeout() != null) {
      queryTimeout = mappedStatement.getTimeout();
    // 最后是configuration的默认超时时间
    } else if (configuration.getDefaultStatementTimeout() != null) {
      queryTimeout = configuration.getDefaultStatementTimeout();
    }
    if (queryTimeout != null) {
      stmt.setQueryTimeout(queryTimeout);
    }
    // 第一优先级是事务中设置的超时时间
    StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
  }

public static void applyTransactionTimeout(Statement statement, Integer queryTimeout, Integer transactionTimeout) throws SQLException {
    if (transactionTimeout == null) {
      return;
    }
    if (queryTimeout == null || queryTimeout == 0 || transactionTimeout < queryTimeout) {
      statement.setQueryTimeout(transactionTimeout);
    }
  }

prepare()处理完之后,回到上个方法中,可以看到 parameterize() 方法内部最终会去调用setParameters()方法,这个方法其实就是根据你的paramtermappings配置去将入参通过typehandler去精准映射, typeHandler.setParameter 方法就是往statment中塞参数。

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> 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();
          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 = 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);
          }
        }
      }
    }
  }

query()方法内部,其实就是调用execute()方法,然后将结果通过ResultSetHandler结果映射到反射生成的实体类中。

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

handleResultSets()方法中主要逻辑是遍历resultmap,然后将获取的结果通过 metaObject 反射赋值,首先会去获取resultSet的第一个值,这是循环的开始,然后验证一下结果是否为空,便进入循环,每次通过getNextResultSet()方法获取下一个值,然后通过handleResultSet()去赋值,退出循环的条件就是getNextResultSet()方法获取不到值。

  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;
    // 从statement中获取 结果
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取resultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 验证resultmap是否为空
    validateResultMapsCount(rsw, resultMapCount);
    // 循环直到所有的结果全部处理完
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 设值,通过metaObject
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // 解析resultSet,如果有复杂嵌套map也去解析,一般没有resultset,这里所以不走
    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()方法中完成的,在这个方法内部,最终会去执行handleRowValues()这个方法,handleRowValues()内部,有个嵌套map的逻辑分支,如果有复杂映射则会递归去处理,如果没有则直接调用handleRowValuesForSimpleResultMap()方法。

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      // 这里为空
      if (parentMapping != null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

handleRowValuesForSimpleResultMap()方法内部,重要的其实就两行逻辑,getRowValue()与storeObject()。

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
   	  // 反射赋值就是在getRowValue中做的
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // 将结果放到resultHandler中存储起来
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }

getRowValue()方法完成反射赋值调用相应对象的set()方法这个过程,虽然代码很乱,但是其实都是一些准备条件,具体的赋值逻辑是在 applyPropertyMappings() 方法中,这个方法中有个metaObject.setValue(property, value);,这个方法就是mybatis反射赋值的一个处理点。

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

循环处理完成后,mybatis已经完成了将数据库中的数据查出来放到对应的实体类中这个过程,到这里preparestatment 的执行也就结束了。
mybatis中关于反射有一个非常不错的设计,是在reflect包下,包括赋值、拷贝对象都是由他去完成的,下一章会介绍这个。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值