StatementHandler-Mybatis源码系列
内容更新github地址:我飞
StatementHandler接口
StatementHandler封装了Mybatis连接数据库操作最基础的部分。因为,无论怎么封装,最终我们都是要使用JDBC和数据库打交道的。
最早我们学习java连接数据库时的代码就像下面写的那样::
import java.sql.*;
public class FirstExample {
   // JDBC driver name and database URL
   static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
   static final String DB_URL = "jdbc:mysql://localhost/emp";
   //  Database credentials
   static final String USER = "root";
   static final String PASS = "123456";
   public static void main(String[] args) {
   Connection conn = null;
   Statement stmt = null;
   try{
      //STEP 2: Register JDBC driver
      Class.forName("com.mysql.jdbc.Driver");
      //STEP 3: Open a connection
      System.out.println("Connecting to database...");
      conn = DriverManager.getConnection(DB_URL,USER,PASS);
      //STEP 4: Execute a query
      System.out.println("Creating statement...");
      stmt = conn.createStatement();
      String sql;
      sql = "SELECT id, first, last, age FROM Employees";
      ResultSet rs = stmt.executeQuery(sql);
      //STEP 5: Extract data from result set
      while(rs.next()){
         //Retrieve by column name
         int id  = rs.getInt("id");
         int age = rs.getInt("age");
         String first = rs.getString("first");
         String last = rs.getString("last");
         //Display values
         System.out.print("ID: " + id);
         System.out.print(", Age: " + age);
         System.out.print(", First: " + first);
         System.out.println(", Last: " + last);
      }
      //STEP 6: Clean-up environment
      rs.close();
      stmt.close();
      conn.close();
   }catch(SQLException se){
      //Handle errors for JDBC
      se.printStackTrace();
   }catch(Exception e){
      //Handle errors for Class.forName
      e.printStackTrace();
   }finally{
      //finally block used to close resources
      try{
         if(stmt!=null)
            stmt.close();
      }catch(SQLException se2){
      }// nothing we can do
      try{
         if(conn!=null)
            conn.close();
      }catch(SQLException se){
         se.printStackTrace();
      }//end finally try
   }//end try
   System.out.println("There are so thing wrong!");
}//end main
}//end FirstExample
而对于StatementHandler来说就是将下面的代码进行了封装和抽象,将和数据库交互的能力提供给Mybatis上层应用
StatementHandler接口:
public interface StatementHandler {
  // 从connection中获取statement
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  // 对sql进行设置参数
  void parameterize(Statement statement)
      throws SQLException;
  // 批量执行
  void batch(Statement statement)
      throws SQLException;
  // 执行预编译后的sql语句(update,delete,insert)
  int update(Statement statement)
      throws SQLException;
  // 执行查询sql
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  // 使用游标执行查询sql
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  // 获取执行SQL语句的封装类BoundSql
  BoundSql getBoundSql();
  // 参数处理器
  ParameterHandler getParameterHandler();
}
先看一下接口下面的实现类关系:

BaseStatementHandler
BaseStatementHandler作为继承StatementHandler接口的抽象类存在。
public abstract class BaseStatementHandler implements StatementHandler {
  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  protected final ParameterHandler parameterHandler;
  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  protected final RowBounds rowBounds;
  protected BoundSql boundSql;
  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);
  }
  @Override
  public BoundSql getBoundSql() {
    return boundSql;
  }
  @Override
  public ParameterHandler getParameterHandler() {
    return parameterHandler;
  }
  @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;
  protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
    Integer queryTimeout = null;
    if (mappedStatement.getTimeout() != null) {
      queryTimeout = mappedStatement.getTimeout();
    } else if (configuration.getDefaultStatementTimeout() != null) {
      queryTimeout = configuration.getDefaultStatementTimeout();
    }
    if (queryTimeout != null) {
      stmt.setQueryTimeout(queryTimeout);
    }
    StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
  }
  protected void setFetchSize(Statement stmt) throws SQLException {
    Integer fetchSize = mappedStatement.getFetchSize();
    if (fetchSize != null) {
      stmt.setFetchSize(fetchSize);
      return;
    }
    Integer defaultFetchSize = configuration.getDefaultFetchSize();
    if (defaultFetchSize != null) {
      stmt.setFetchSize(defaultFetchSize);
    }
  }
  protected void closeStatement(Statement statement) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      //ignore
    }
  }
  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }
}
它内部维护了核心字段:
- parameterHandler 作用就是将参数替换sql中的占位符的功能
- resultSetHandler 将sql结果集映射成结果对象
- executor 执行sql的抽象,后续详细看
只实现了三个接口方法:
- getBoundSql
- getParameterHandler
- prepare 其中prepare的实现使用了模版模式,子类必须实现instantiateStatement方法来完成父类prepare方法的模版。
在BaseStatementHandler的构造函数中我们可以看到一段调用generateKeys的代码,它和生成主键key有关。
关于获得主键key和插入数据时放入主键key牵涉到以下几个配置:
- selectKey
- useGeneratedKeys 设置true使用自动生成的主键
- keyProperty 指定主键是(javaBean的)哪个属性。
对于支持自动生成记录主键的数据库,如:MySQL,SQL Server,此时设置useGeneratedKeys参数值为true,在执行添加记录之后可以获取到数据库自动生成的主键ID,合keyProperty指定主键。
例子:
  <insert id="saveMsg" parameterType="cn.com.tt.e.nano.Notice"
        useGeneratedKeys="true" keyProperty="msgId">
        insert into notice(msg_type,title,content,rec_time,send_time,user_id,deleted,viewed)
        values(#{msgType,jdbcType=INTEGER},#{title,jdbcType=VARCHAR},#{content,jdbcType=VARCHAR},
               #{recTime,jdbcType=BIGINT},#{sendTime,jdbcType=BIGINT},#{userId,jdbcType=VARCHAR},
               #{deleted,jdbcType=TINYINT},#{viewed,jdbcType=INTEGER})
    </insert>
如果使用selectKey,可以设置order属性为AFTER。
例子:
<insert id="insertAndgetkey" parameterType="com.soft.mybatis.model.User">
        <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into t_user (username,password,create_date) values(#{username},#{password},#{createDate})
    </insert>
对于Oracle数据库,当要用到自增字段时,需要用到Sequence或者使用外界传入的唯一值比如uuid。则也使用selectKey,设置order为before。
例子:
<insert id="insert"  parameterType="com.lzumetal.mybatis.entity.Employee">
    <selectKey keyProperty="id" resultType="long" order="BEFORE">
    SELECT SEQ_ADMIN.NEXTVAL FROM DUAL
    </selectKey>
    INSERT INTO tbl_employee(id, name, age, create_time)
    VALUES(#{id}, #{name}, #{age}, #{createTime})
</insert>
KeyGenerator
KeyGenerator接口的实现有:Jdbc3KeyGenerator,SelectKeyGenerator,NoKeyGenerator
useGeneratedKeys设置在settings配置文件中的相关源码在MappedStatement的org.apache.ibatis.mapping.MappedStatement.Builder#Builder中:
mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
默认是NoKeyGenerator,如果配置了useGeneratedKeys=true并且是insert操作则使用Jdbc3KeyGenerator。
useGeneratedKeys设置在mapper文件中的相关源码:
if (configuration.hasKeyGenerator(keyStatementId)) {
  keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
  keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
      configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
      ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
如果设置了selectKey则使用SelectKeyGenerator(这部分可以更下去看到),useGeneratedKeys逻辑和前面一样。
selectKey标签中的order分别对应着KeyGenerator中的processBefore方法和processAfter方法。
而前面提到的BaseStatementHandler中generateKeys方法就是触发processBefore的地方,也是唯一一个地方。
  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }
在org.apache.ibatis.executor.statement.StatementHandler#update中的各个实现中都可以看到执行processAfter的代码,比如SimpleStatementHandler的代码:
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }
ParameterHandler
再来看一下前面提到的ParameterHandler,功能就是将动态的sql中的占位符替换成实参。
它的实现是DefaultParameterHandler:
public class DefaultParameterHandler implements ParameterHandler {
  private final TypeHandlerRegistry typeHandlerRegistry;
  private final MappedStatement mappedStatement;
  private final Object parameterObject;
  private final BoundSql boundSql;
  private final Configuration configuration;
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }
  @Override
  public Object getParameterObject() {
    return parameterObject;
  }
  @Override
  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 e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
}
SimpleStatementHandler类
SimpleStatementHandler是BaseStatementHandler的子类,使用Statement来完成数据库的操作,所以Sql中不会有占位符,parameterize就是空实现。
query方法:
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }
最后一步将数据库数据转换成java对象,这个后续展开。
update方法:
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }
其中对主键处理的代码前面已经作了解释,从上面两个代码片段来看已经挖到最深了,最终都是调用java.sql.*的api。
PreparedStatementHandler
PreparedStatementHandler使用PreparedStatement实现。
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleCursorResultSets(ps);
  }
    public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
RoutingStatementHandler
看名字就可以猜到了这个是路由用的,看它的构造函数:
  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());
    }
  }
所以具体使用哪一个StatementHandler是由MappedStatement.getStatementType()决定的
CallableStatementHandler
依赖CallableStatement,用于调用存储过程。
StatementHandler-Mybatis源码系列的更多相关文章
- Mybatis源码系列 执行流程(一)
		1.Mybatis的使用 public static void main(String[] args) throws IOException { //1.获取配置文件流 InputStream is ... 
- MyBatis 源码分析系列文章合集
		1.简介 我从七月份开始阅读MyBatis源码,并在随后的40天内陆续更新了7篇文章.起初,我只是打算通过博客的形式进行分享.但在写作的过程中,发现要分析的代码太多,以至于文章篇幅特别大.在这7篇文章 ... 
- 精尽MyBatis源码分析 - SQL执行过程(二)之 StatementHandler
		该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ... 
- 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)
		上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)>为大家介绍了mybatis中别名的使用,以及其源码.本篇将为大家介绍TypeH ... 
- 深入浅出Mybatis系列(四)---配置详解之typeAliases别名(mybatis源码篇)
		上篇文章<深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)> 介绍了properties与environments, ... 
- 深入浅出Mybatis系列(三)---配置详解之properties与environments(mybatis源码篇)
		上篇文章<深入浅出Mybatis系列(二)---配置简介(mybatis源码篇)>我们通过对mybatis源码的简单分析,可看出,在mybatis配置文件中,在configuration根 ... 
- Mybatis源码分析-StatementHandler
		承接前文Mybatis源码分析-BaseExecutor,本文则对通过StatementHandler接口完成数据库的CRUD操作作简单的分析 StatementHandler#接口列表 //获取St ... 
- MyBatis 源码分析系列文章导读
		1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ... 
- 深入浅出Mybatis系列五-TypeHandler简介及配置(mybatis源码篇)
		注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(四)---配置详解之typeAliase ... 
- 深入浅出Mybatis系列四-配置详解之typeAliases别名(mybatis源码篇)
		注:本文转载自南轲梦 注:博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 上篇文章<深入浅出Mybatis系列(三)---配置详解之properties ... 
随机推荐
- PowerDesigner设计表时显示注释列Comment,Columns中没有Comment的解决办法
			我使用的PowerDesigner版本为16.5,如下图: 在所要编辑的表上双击,打开Table Properties窗口,并将上面的选项卡切换到Columns,如下图: 我们点击Customize ... 
- 手动配置webpack之React
			安装 1.安装react转译相关依赖包: npm安装: npm install --save-dev babel-core babel-loader babel-preset- ... 
- c# new三种用法
			前几天去家公司面试,有一道这样的题:写出c#中new关键字的三种用法,思前想后挖空心思也只想出了两种用法,回来查了下msdn,还真是有第三种用法:用于在泛型声明中约束可能用作类型参数的参数的类型,这是 ... 
- 洛谷P2289 [HNOI2004]邮递员(插头dp)
			传送门 太神仙了……讲不来讲不来->这里 //minamoto #include<iostream> #include<cstdio> #include<cstri ... 
- 使用CCProxy代理遇到的问题
			第一种:打开CCProxy后,客户端电脑无法连接上. 解决方案:主机是否开启了360里面的局域网隐藏,这个一定要关闭,否则客户端无法找到主机.另外一种情况,主机关闭局域网隐藏之后,一直遭受ARP攻击, ... 
- 剑指Offer的学习笔记(C#篇)-- 用两个栈实现队列
			题目描述 用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. 一 . 概念! 首先要理解栈和队列的概念. 1. 栈:咱可以简单的把栈理解成装羽毛球的球桶.或者我们吃的 ... 
- Nacos深入浅出(四)
			private void executeAsyncInvoke() { while (!queue.isEmpty()) { NotifySingleTask task = queue.poll(); ... 
- CSS样式之操作属性二
			********css样式之属性操作******** 一.文本属性 1.text-align:cnter 文本居中 2.line heigth 垂直居中 :行高,和高度对应 3.vertical-al ... 
- 解决git commit报错问题
			参考: https://stackoverflow.com/questions/3239274/git-commit-fails-due-to-insufficient-permissions 问题 ... 
- 微软官方NET Core 2.0
			NET Core 2.0 微软官方发布的.NET Core 2.0相关的博客: Announcing .NET Standard 2.0 Announcing .NET Core 2.0 F# and ... 
