• MyBatis框架的使用及源码分析(十一) StatementHandler


    我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Excecutor执行sql操作的最终都调用了StatementHandler 来执行,我们拿SimpleExecutor来看:

      public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {
          Configuration configuration = ms.getConfiguration();//获得配置
          //获得statementHandler里面有statement,来处理 
          StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
          stmt = prepareStatement(handler, ms.getStatementLog());
          return handler.update(stmt);//最终是一个statement进行处理 
        } finally {
          closeStatement(stmt);
        }
      }
    
      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());
          return handler.<E>query(stmt, resultHandler);
        } finally {
          closeStatement(stmt);
        }
      }

    可以看出,Executor本质上也是个中介,具体的事情原来是StatementHandler来完成的。在它这里会使用parameterHandler和ResultHandler对象为我们绑定SQL参数和组装最后的结果返回。

    我们先来看看statementHandler接口的定义:

    package org.apache.ibatis.executor.statement;  
      
    import java.sql.Connection;  
    import java.sql.SQLException;  
    import java.sql.Statement;  
    import java.util.List;  
      
    import org.apache.ibatis.cursor.Cursor;  
    import org.apache.ibatis.executor.parameter.ParameterHandler;  
    import org.apache.ibatis.mapping.BoundSql;  
    import org.apache.ibatis.session.ResultHandler;  
      
    /** 
     * @author Clinton Begin 
     */  
    public interface StatementHandler {  
      //获取Statement  
      Statement prepare(Connection connection, Integer transactionTimeout)  
          throws SQLException;  
      //设置参数 
      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 
      ParameterHandler getParameterHandler();  
      
    }  

     在MyBatis实现了StatementHandler 的有四个类:
    RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。
    SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。
    PreparedStatementHandler 这个用于预编译参数SQL的运行。
    CallableStatementHandler 它将实存储过程的调度。

    在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象:

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

    然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。

    org.apache.ibatis.executor.statement.RoutingStatementHandler

    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的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。

    @Override  
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {  
      ErrorContext.instance().sql(boundSql.getSql());  
      Statement statement = null;  
      try {  
        statement = instantiateStatement(connection);  
        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);  
      }  
    }  
      
    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;  

    instantiateStatement是一个抽象方法,那么它就有其实现类。那就是之前说的那几个具体的StatementHandler对象,让我们看看PreparedStatementHandler:

    @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() != null) {  
        return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
      } else {  
        return connection.prepareStatement(sql);  
      }  
    }  

    这个方法非常简单,我们可以看到它主要是根据上下文来预编译SQL,这是我们还没有设置参数。设置参数的任务是交由,statement接口的parameterize方法来实现的。

    parameterize方法

    上面在prepare方法里面预编译了SQL。那么我们这个时候希望设置参数。在Statement中我们是使用parameterize方法进行设置参数的。让我们看看PreparedStatementHandler中的parameterize方法:

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

    很显然这里很简单是通过parameterHandler来实现的,后面我们专门写一篇文章讲解parameterHandler的时候再来看它如何实现。

    query/update方法

    我们用了prepare方法预编译了SQL,用了parameterize方法设置参数,那么我们接下来肯定是想执行SQL,而SQL无非是两种:
    一种是进行查询——query,另外就是更新——update。
    这些方法都很简单,让我们看看PreparedStatementHandler的实现:

    @Override  
    public int update(Statement statement) throws SQLException {  
      PreparedStatement ps = (PreparedStatement) statement;  
      ps.execute();  
      int rows = ps.getUpdateCount();  
      Object parameterObject = boundSql.getParameterObject();  
      KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
      keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);  
      return rows;  
    }  
      
      @Override  
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
      PreparedStatement ps = (PreparedStatement) statement;  
      ps.execute();  
      return resultSetHandler.<E> handleResultSets(ps);  
    }  
      
    @Override  
    public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {  
      PreparedStatement ps = (PreparedStatement) statement;  
      ps.execute();  
      return resultSetHandler.<E> handleCursorResultSets(ps);  
    }  

    我们可以看到如果是进行update的,它将会执行生成主键的操作(插入数据要自动生成主键的时候),然后就返回影响行数。
    如果是进行query的就更加简单了,它就是执行SQL语句,然后讲结果使用resultHandler的handleResultSets去完成我们的结果组装。

    batch方法

    参考parameterHandler的具体实现:

    @Override  
    public void batch(Statement statement) throws SQLException {  
      PreparedStatement ps = (PreparedStatement) statement;  
      ps.addBatch();  
    }  

    执行批量操作。

    当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。
    当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。
    我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。
    懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

  • 相关阅读:
    实用的css3小实例---1、卡片效果
    CSS3疑难问题---1、px、em、rem的区别
    心得体悟帖---200106(录课本质)(工具人)
    心得体悟帖---200105(心性不够容易失去)
    心得体悟帖---200105(legend3做好升段)(还不够厉害)
    心得体悟帖---200104(每天运动)(危急存亡)
    webstorm2019.2永久破解教程(亲测有效)
    范仁义css3课程---6、文本样式1
    范仁义css3课程---7、文本2(css3文本)
    css文本样式总结
  • 原文地址:https://www.cnblogs.com/zsg88/p/7566097.html
Copyright © 2020-2023  润新知