美文网首页Mybatis
MyBatis原理系列(五)-手把手带你了解Statement、

MyBatis原理系列(五)-手把手带你了解Statement、

作者: Renaissance_ | 来源:发表于2020-12-24 17:21 被阅读0次

    MyBatis原理系列(一)-手把手带你阅读MyBatis源码
    MyBatis原理系列(二)-手把手带你了解MyBatis的启动流程
    MyBatis原理系列(三)-手把手带你了解SqlSession,SqlSessionFactory,SqlSessionFactoryBuilder的关系
    MyBatis原理系列(四)-手把手带你了解MyBatis的Executor执行器
    MyBatis原理系列(五)-手把手带你了解Statement、StatementHandler、MappedStatement间的关系
    MyBatis原理系列(六)-手把手带你了解BoundSql的创建过程
    MyBatis原理系列(七)-手把手带你了解如何自定义插件
    MyBatis原理系列(八)-手把手带你了解一级缓存和二级缓存
    MyBatis原理系列(九)-手把手带你了解MyBatis事务管理机制

    在上篇文章中我们介绍了Executor的三种执行器,并且在执行update和query方法的时候,都会有一个Statement和StatementHandler的操作。当时我们一笔带过了,在这篇文章中我们将介绍Statement,StatementHandler的关系,以及它们的实现细节。

    1. Statement 对象

    我们先来了解下Statement对象,在原始JDBC操作中,会有加载驱动,设置属性,获取连接,创建Statement对象...等一系列操作。Statement对象在JDBC操作中就是向数据库发送sql语句,并获取到执行结果。Statement对象有三种,分别是Statement,PreparedStatement,CallableStatement。它们的继承关系如下

    Statement继承关系
    1. Statement:可以发送字符串类型的sql,不支持传递参数。适用于静态sql语句。
    2. PreparedStatement: 预编译的sql语句,接受参数传入,并且可以防止sql注入,提高安全性。Sql语句会编译在数据库系统,适用于多次执行相同的sql,因此性能高于Statement。
    3. CallableStatement:在执行存储过程时使用,并且可以接受参数传入。

    Statement 接口方法有这么多,主要就是执行更新,查询,获取结果等操作。

    Statement接口方法

    2. StatementHandler 对象

    2.1 StatementHandler 对象初识

    StatementHandler 对象从字面意义上来讲就是管理Statement对象的了。它有两个直接实现,一个是BaseStatementHandler,另一个是RoutingStatementHandler。然后BaseStatementHandler有三个实现分别是SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler,他们分别管理的就是上面讲到的Statement,PreparedStatement和CallableStatement对象了。继承关系如下图,是不是很像Executor的继承关系,BaseStatementHandler是使用了适配器模式,减少了实现接口的复杂性,RoutingStatementHandler则是包装了以上三种Handler,作为一个代理类进行操作。

    StatementHandler继承关系

    StatementHandler 接口的方法如下

    /**
     * @author Clinton Begin
     */
    public interface StatementHandler {
    
      // 创建Statement对象
      Statement prepare(Connection connection, Integer transactionTimeout)
          throws SQLException;
    
      // 对Sql中的占位符进行赋值
      void parameterize(Statement statement)
          throws SQLException;
      
      // 添加到批处理操作中
      void batch(Statement statement)
          throws SQLException;
      
      // 执行更新操作
      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对象
      BoundSql getBoundSql();
    
      // 获取参数处理器
      ParameterHandler getParameterHandler();
    
    }
    
    2.2 StatementHandler 对象创建

    StatementHandler 是在哪里创建的呢?在手把手带你了解MyBatis的Executor执行器中,在执行doQuery和doUpdate方法时,都会创建StatementHandler对象。
    以SimpleExecutor为例,创建StatementHandler对象实际由Configuration对象创建的。

    // SimpleExecutor的doUpdate方法
    @Override
      public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();
          // 1. 创建StatementHandler
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
          // 2. 创建Statement
          stmt = prepareStatement(handler, ms.getStatementLog());
          // 3. 执行sql操作
          return handler.update(stmt);
        } finally {
          // 2. 关闭Statement
          closeStatement(stmt);
        }
      }
    

    Configuration的newStatementHandler方法中,创建的是RoutingStatementHandler对象。我们知道RoutingStatementHandler实际是对三种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;
      }
    

    继续点击去看,根据MappedStatement对象的类型,创建出具体的StatementHandler对象。如果MappedStatement没有指出具体的StatementType(),那么StatementType默认是PREPARED类型的。

    // 实际的处理器,SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三种处理器中的一种
      private final StatementHandler delegate;
    
      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());
        }
    
      }
    

    接下来,我们看看PreparedStatementHandler创建的过程,实际调用的是BaseStatementHandler的构造方法。

    // PreparedStatementHandler的构造方法
     public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
       // 实际调用的是BaseStatementHandler的构造方法
       super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
     }
    

    至此,我们了解到了,其实三种StatementHandler都是用的BaseStatementHandler的构造方法创建的。

    protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        this.configuration = mappedStatement.getConfiguration();
        this.executor = executor;
        this.mappedStatement = mappedStatement;
        this.rowBounds = rowBounds;
    
        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        this.objectFactory = configuration.getObjectFactory();
    
        if (boundSql == null) { // issue #435, get the key before calculating the statement
          generateKeys(parameterObject);
          boundSql = mappedStatement.getBoundSql(parameterObject);
        }
    
        this.boundSql = boundSql;
    
        this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
      }
    
    2.3 Statement对象创建

    StatementHandler对象创建出来了,就可以创建Statement对象了。也是以SimpleExecutor执行器为例子。

          // 1. 创建StatementHandler
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
          // 2. 创建Statement
          stmt = prepareStatement(handler, ms.getStatementLog());
          ...
    

    SimpleExecutor 的 prepareStatement方法 中主要做了以下三步:

    1. 获取数据库连接
    2. 创建Statement对象
    3. 设置sql参数
    // SimpleExecutor 的 prepareStatement方法
    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 1. 获取数据库连接
        Connection connection = getConnection(statementLog);
        // 2. 创建Statement对象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 3. 设置sql参数
        handler.parameterize(stmt);
        return stmt;
      }
    

    我们继续看看prepare()方法做了什么,这个方法BaseStatementHandler给出了默认实现,因此三个StatementHandler用的都是这个实现。主要做了以下工作

    1. 初始化Statement对象
    2. 设置超时时间
    3. 设置查询大小
    4. 出现异常关闭Statement对象
    // BaseStatementHandler的prepare方法
     @Override
      public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
          // 1. 初始化Statement对象
          statement = instantiateStatement(connection);
          // 2. 设置超时时间
          setStatementTimeout(statement, transactionTimeout);
          // 3. 设置查询大小
          setFetchSize(statement);
          return statement;
        } catch (SQLException e) {
          // 4. 关闭Statement对象
          closeStatement(statement);
          throw e;
        } catch (Exception e) {
          closeStatement(statement);
          throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
        }
      }
    

    可以Statement对象的初始化操作是在instantiateStatement方法中进行的,我们继续看看instantiateStatement这个方法又做了什么操作。好的,在BaseStatementHandler中instantiateStatement方法被设计为抽象方法,由子类实现,这点也体现出了模板方法的设计模式。

    // BaseStatementHandler的instantiateStatement方法
    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
    

    现在以SimpleStatementHandler为例子,最终调用的还是connection.createStatement()方法,回到了最初的起点,也就是MyBatis对JDBC操作进行了包装。

    @Override
      protected Statement instantiateStatement(Connection connection) throws SQLException {
        // 实际还是调用的connection.createStatement()方法
        if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
          return connection.createStatement();
        } else {
          return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
      }
    

    获取到了Statement 对象,就可以快乐执行execute方法,向数据库发送sql语句执行了。

    3. MappedStatement 对象

    有眼尖的同学在前面会看到Executor在执行doUpdate的时候,会传入MappedStatement对象,那么MappedStatement和Statement,StatementHandler对象间有什么关联呢。
    我们在用MyBatis配置sql的时候,insert/update/delete/select等标签下面都会包含一段sql,MappedStatement就是对sql标签的信息描述。

    // 一个select标签会对应一个MappedStatement对象
     <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
        select 
        <include refid="Base_Column_List" />
        from t_test_user
        where id = #{id,jdbcType=BIGINT}
      </select>
    

    MappedStatement 类的私有属性如下

     // mapper配置文件名,如:userMapper.xml
      private String resource;
      // 配置类
      private Configuration configuration;
      // 命名空间+标签id 如com.example.demo.dao.TTestUserMapper.selectByPrimaryKey
      private String id;
      private Integer fetchSize;
      // 超时时间
      private Integer timeout;
      // sql对象类型 STATEMENT, PREPARED, CALLABLE 三种之一
      private StatementType statementType;
      // 结果集类型
      private ResultSetType resultSetType;
      // sql语句
      private SqlSource sqlSource;
      // 缓存
      private Cache cache;
      // 参数映射关系
      private ParameterMap parameterMap;
      // 结果映射关系,可以自定义多个ResultMap
      private List<ResultMap> resultMaps;
      private boolean flushCacheRequired;
      // 是否启用缓存
      private boolean useCache;
      // 结果是否排序
      private boolean resultOrdered;
      // sql语句类型,INSERT, UPDATE, DELETE, SELECT
      private SqlCommandType sqlCommandType;
      private KeyGenerator keyGenerator;
      private String[] keyProperties;
      private String[] keyColumns;
      private boolean hasNestedResultMaps;
      private String databaseId;
      private Log statementLog;
      private LanguageDriver lang;
      private String[] resultSets;
    

    其中最主要的方法还是getBoundSql方法,对动态sql进行解析,获取最终的sql语句。关于SqlSource,BoundSql相关的内容我们将在其它文章中介绍。

      public BoundSql getBoundSql(Object parameterObject) {
        // 获取BoundSql对象,BoundSql对象是对动态sql的解析
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        // 获取参数映射
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
          boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }
    
        // check for nested result maps in parameter mappings (issue #30)
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
          String rmId = pm.getResultMapId();
          if (rmId != null) {
            ResultMap rm = configuration.getResultMap(rmId);
            if (rm != null) {
              hasNestedResultMaps |= rm.hasNestedResultMaps();
            }
          }
        }
    
        return boundSql;
      }
    

    4. 总结

    这篇文章带大家详细了解Statement对象,StatementHandler以及其三种实现,还有MappedStatement也做了简单介绍,Statement对象作为主要组件,了解其创建和原理对我们整体了解MyBatis会很有帮助。

    相关文章

      网友评论

        本文标题:MyBatis原理系列(五)-手把手带你了解Statement、

        本文链接:https://www.haomeiwen.com/subject/wfkunktx.html