MyBatis 源码分析-04 - MyBatis Statement 的生成流程

限于篇幅, **本文主要分析内容是 SimpleExecutor.prepareStatement(handler, ms.getStatementLog())这个方法. **

image-20210322114548948

Version :

  • MyBatis : 3.5.5

  • 项目地址 :MyBatis-source-learn

  • PS: 若文章字体偏大或者偏小,建议通过 ctrl键+鼠标滑轮 进行修改,以提升阅读效果.(带来不便,请谅解!)

流程:

传统JDBC连接数据的方式:
	1. 获取DataSource 2. 获取连接 Connection 3. 生成Statement 4. 执行Statement 5. 处理结果集ResultSet
       	6. 关闭ResultSet 7. 关闭Statement 8. 关闭Connection 9.返回结果
Mybatis 执行步骤也是类似的:
	1. 通过Transaction获取Connection(管理Connection) 2. Executor处理方法参数生成statement(管理Statement)  3. ResultSetGHandler 处理ResultSet(开启关闭)  

测试代码:

ps :其他代码请自己去MyBatis-source-learn查看

测试类:

  • CreateSqlSession.java
public class CreateSqlSession {

    public static void main(String[] args) {
//         1. 连接数据库,配置基本参数
        DataSource dataSource = getDataSource();
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        Environment environment = new Environment("development",  transactionFactory, dataSource);
         Configuration configuration = new Configuration(environment);
//         2.  处理XXXMapper, 生成SqlSource(即处理xml 以及注解生成的SQL语句 与PreparedStatement绑定)
        configuration.addMapper(BlogMapper.class);
        // 3.  获取SqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = sqlSessionFactory.openSession();
//         4. 获取mapper,执行方法
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        for (int i = 0; i < 10; i++) {
            Blog blog = mapper.selectBlog(1);
            System.out.println("i: "+blog);
        }
    }

**PS: MyBatis 默认查询 顺序是 二级缓存 (CachedExecutor.cache) -> 一级缓存(BaseExecutor.localCache) -> 数据库. **

人话描述总流程:

  1. 生成statementHandelr(resultSethandler,)

    1. StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  2. 生成Statement

    1. stmt = prepareStatement(handler, ms.getStatementLog());//对statement 进行处理,绑定参数到sql中

      1. 获取Connection : getConnection(statementLog)

      2. 生成Statement , 绑定 方法参数

        1. 实例化Statement: stmt = handler.prepare(connection, transaction.getTimeout());

          结果: stmt = com.mysql.cj.jdbc.ClientPreparedStatement: select * from Blog where id = ** NOT SPECIFIED **

        2. 处理方法参数 : handler.parameterize(stmt);// 处理参数 绑定 sql - java type

          结果: stmt= com.mysql.cj.jdbc.ClientPreparedStatement: select * from Blog where id = 1

源码分析:

  • SimpleExecutor.java

  @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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());//对statement 进行处理,绑定参数到sql中
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }  
}

1. 生成PreParedStatementHandler

  • Configuration.java
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 // 初始化statement (prepareStatement.callableStatement) ,获取代理对象
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);//初始化statement
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);// 添加拦截器, 拦截statement
  return statementHandler;
}
PreparedStatementHandler类图

PreparedStatementHandler

2. 创建Statement

SimpleExecutor.java

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);//利用事务获取connection
  stmt = handler.prepare(connection, transaction.getTimeout());
  handler.parameterize(stmt);// 处理参数 绑定 sql - java type
  return stmt;
}
SimpleExecutor的类图
SimpleExecutor
获取Connection
protected Connection getConnection(Log statementLog) throws SQLException {
  Connection connection = transaction.getConnection();//事务管理connection
  if (statementLog.isDebugEnabled()) {
    return ConnectionLogger.newInstance(connection, statementLog, queryStack);
  } else {
    return connection;
  }
}
生成Statement
  • BaseStatementHandler.java
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    statement = instantiateStatement(connection);// 获取statement (preparedStatement, callablestatment)
    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);
  }
}
instantiateStatement(connection)
  • PreParedStatementHandler.java
@Override
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);
    }
  } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {// 处理结果集类型
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}
执行结果:

image-20210322121954900

处理方法参数:
  • PreparedStatementHandler.java
@Override
public void parameterize(Statement statement) throws SQLException {
  parameterHandler.setParameters((PreparedStatement) statement);
}
  • DefaultParameterHandler.java

    PS : 可以跳过, 只是对方法参数进行绑定, 然后调用Statement.setXXX(value)方式填充statement


@Override//// TODO: 2021/3/16 给statement 加方法 参数
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);//preparedStatement.setInt(),setBlob
        } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}
执行结果:

image-20210322121222800

总结:

  • 整体流程:这!

  • Mybatis 默认查询方法的执行流程是: 二级缓存 -> 一级缓存 -> DB

  • 一级缓存 LocalCache: localCacheScope= session( default), 建议修改为 statement ,session 存在脏读现象

  • 二级缓存 Cache: cacheEnabled=true(default), 对应xxxMapper.xml 添加 即可使用,使用二级缓存,可以有效避免在单表查询时出现的脏读现象,但是在多表查询时,极大可能出现脏数据,存在缺陷.

参考:

  • https://mybatis.org/mybatis-3/zh