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

先看一下接口下面的實現類關係:
8ce28d16d5aa09bb3eb535a3370bc481.png

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接口的實現有:Jdbc3KeyGeneratorSelectKeyGeneratorNoKeyGenerator

useGeneratedKeys設置在settings配置文件中的相關源碼在MappedStatementorg.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,用於調用存儲過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章