MyBatis原理第四篇——statementHandler对象(sqlSession内部核心实现,插件的基础)

首先约定文中将的四大对象是指:executor, statementHandler,parameterHandler,resultHandler对象。(为了方便下面的文章说道四大对象就专指它们)

讲到statementHandler,毫无疑问它是我们四大对象最重要的一个,它的任务就是和数据库对话。在它这里会使用parameterHandler和ResultHandler对象为我们绑定SQL参数和组装最后的结果返回。

一、statementHandler对象的定义:

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

 

[java] view plain copy
 
  1. public interface StatementHandler {  
  2.   
  3.   Statement prepare(Connection connection)  
  4.       throws SQLException;  
  5.   
  6.   void parameterize(Statement statement)  
  7.       throws SQLException;  
  8.   
  9.   void batch(Statement statement)  
  10.       throws SQLException;  
  11.   
  12.   int update(Statement statement)  
  13.       throws SQLException;  
  14.   
  15.   <E> List<E> query(Statement statement, ResultHandler resultHandler)  
  16.       throws SQLException;  
  17.   
  18.   BoundSql getBoundSql();  
  19.   
  20.   ParameterHandler getParameterHandler();  
  21.   
  22. }  

 

这里有几个重要的方法,prepare,parameterize和query,update,他们的作用是不一样的。

在MyBatis实现了statementHandler的有四个类:

RoutingStatementHandler,这是一个封装类,它不提供具体的实现,只是根据Executor的类型,创建不同的类型StatementHandler。

SimpleStatementHandler,这个类对应于JDBC的Statement对象,用于没有预编译参数的SQL的运行。

PreparedStatementHandler 这个用于预编译参数SQL的运行。

CallableStatementHandler 它将实存储过程的调度。

 

在MyBatis中,Configuration对象会采用new RoutingStatementHandler()来生成StatementHandler对象,换句话说我们真正使用的是RoutingStatementHandler对象,然后它会根据Executor的类型去创建对应具体的statementHandler对象(SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler)。

然后利用具体statementHandler的方法完成所需要的功能。那么这个具体的statementHandler是保存在RoutingStatementHandler对象的delegate属性的,所以当我们拦截statementHandler的时候就要常常访问它了。它们的关系如下图所示。

 

 

二、prepare方法

首先prepare方法是用来编译SQL的,让我们看看它的源码实现。这里我们看到了BaseStatementHandler对prepare方法的实现,

 

[java] view plain copy
 
  1. @Override  
  2.  public Statement prepare(Connection connection) throws SQLException {  
  3.    ErrorContext.instance().sql(boundSql.getSql());  
  4.    Statement statement = null;  
  5.    try {  
  6.      statement = instantiateStatement(connection);  
  7.      setStatementTimeout(statement);  
  8.      setFetchSize(statement);  
  9.      return statement;  
  10.    } catch (SQLException e) {  
  11.      closeStatement(statement);  
  12.      throw e;  
  13.    } catch (Exception e) {  
  14.      closeStatement(statement);  
  15.      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);  
  16.    }  
  17.  }  
  18.   
  19.  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;  


显然我们通过源码更加关注抽象方法instantiateStatement是做了什么事情。它依旧是一个抽象方法,那么它就有其实现类。那就是之前说的那几个具体的StatementHandler对象,让我们看看PreparedStatementHandler:

 

 

[java] view plain copy
 
  1. @Override  
  2.   protected Statement instantiateStatement(Connection connection) throws SQLException {  
  3.     String sql = boundSql.getSql();  
  4.     if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {  
  5.       String[] keyColumnNames = mappedStatement.getKeyColumns();  
  6.       if (keyColumnNames == null) {  
  7.         return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);  
  8.       } else {  
  9.         return connection.prepareStatement(sql, keyColumnNames);  
  10.       }  
  11.     } else if (mappedStatement.getResultSetType() != null) {  
  12.       return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);  
  13.     } else {  
  14.       return connection.prepareStatement(sql);  
  15.     }  
  16.   }  

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

 

 

 

3、parameterize方法:

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

让我们看看PreparedStatementHandler中的parameterize方法:

 

[java] view plain copy
 
  1. @Override  
  2.   public void parameterize(Statement statement) throws SQLException {  
  3.     parameterHandler.setParameters((PreparedStatement) statement);  
  4.   }  

很显然这里很简单是通过parameterHandler来实现的,我们这篇文章只是停留在statementhandler的程度,等我们讲解parameterHandler的时候再来看它如何实现吧,期待一下吧。

 

 

4、query/update方法

我们用了prepare方法预编译了SQL,用了parameterize方法设置参数,那么我们接下来肯定是想执行SQL,而SQL无非是两种:

一种是进行查询——query,另外就是更新——update。

这些方法都很简单,让我们看看PreparedStatementHandler的实现:

 

[java] view plain copy
 
  1. @Override  
  2.   public int update(Statement statement) throws SQLException {  
  3.     PreparedStatement ps = (PreparedStatement) statement;  
  4.     ps.execute();  
  5.     int rows = ps.getUpdateCount();  
  6.     Object parameterObject = boundSql.getParameterObject();  
  7.     KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();  
  8.     keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);  
  9.     return rows;  
  10.   }  
  11.   
  12. ......  
  13. @Override  
  14.   public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
  15.     PreparedStatement ps = (PreparedStatement) statement;  
  16.     ps.execute();  
  17.     return resultSetHandler.<E> handleResultSets(ps);  
  18.   }  


我们可以看到如果是进行update的,它将会执行生成主键的操作(插入数据要自动生成主键的时候),然后就返回影响行数。

 

如果是进行query的就更加简单了,它就是执行SQL语句,然后讲结果使用resultHandler的handleResultSets去完成我们的结果组装。至于resultHandler的内部实现还是很复杂的,值得期待哦。这里我们暂且不讲等待下一章吧。

 

5、总结

 

StatementHandler是MyBatis四大对象里面最重要的对象,它的方法是十分重要的,也是我们插件的基础。

 

当我们需要改变sql的时候,显然我们要在预编译SQL(prepare方法前加入修改的逻辑)。

当我们需要修改参数的时候我们可以在调用parameterize方法前修改逻辑。或者使用ParameterHandler来改造设置参数。

我们需要控制组装结果集的时候,也可以在query方法前后加入逻辑,或者使用ResultHandler来改造组装结果。

懂的这些方法,才能理解我需要拦截什么对象,如何处理插件,这是MyBatis的核心内容。

 

posted @ 2017-12-11 18:11  start枫  阅读(3744)  评论(0编辑  收藏  举报