MyBatis 源碼分析——生成Statement接口實例


JDBC的知識對於JAVA開發人員來講在簡單不過的知識了。PreparedStatement的作用更是胸有成竹。我們最常見用到有倆個方法:executeQuery方法和executeUpdate方法。這倆個方法之外還有一個execute方法。只是這個方法我們很少用。但是mybatis框架就是卻用這個方法來實現的。不管mybatis用是哪一個方法來實現。有一點可以肯定——那就是必須得到Statement接口實例。你可以這樣子理解mybatis把如何獲得Statement接口實例做了一個完美的封裝。而這一個封裝就是上一章出現的StatementHandler接口。

mybatis里面實現StatementHandler接口有四個類。

RoutingStatementHandler類:筆者把它理解為下面三個類的代理類。

CallableStatementHandler類:對應處理JDBC里面的CallableStatement類。

PreparedStatementHandler類:對應處理JDBC里面的PreparedStatement類。

SimpleStatementHandler類:對應處理JDBC里面的一般Statement接口實例(筆者也不知道JDBC是需叫他什么)。

正如上面所講的筆者把RoutingStatementHandler類理解為三個類的代理類。mybatis並沒有直接去引用后面三個類。而是通過RoutingStatementHandler類來判斷當前到底要調用哪個類。再去執行相關的Statement接口實例。

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

這一段源碼就是前一章尾部源碼的后繼執行。源碼的意圖就是新建一個RoutingStatementHandler類實例。關鍵的點是在RoutingStatementHandle類的構造函數里面。

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

  }

從這里就可以看出筆者為什么說RoutingStatementHandler類可以理解為三個類的代理類。事實上所有的工作都是內部成員delegate來做的。而delegate又是在構造函數里面進行判斷生成的。看樣子在這里JDBC的三種操作方式完美的體現出來。通過MappedStatement的getStatementType方法得到相應返回值,判斷當前SQL語句是要用哪一種操作方式來進行。默認情況下是用Prepared方式。當前筆者不是瞎說的。在MappedStatement的Builder方法里就已經設置了。請讀者們自行查看。

 1  public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
 2       mappedStatement.configuration = configuration;
 3       mappedStatement.id = id;
 4       mappedStatement.sqlSource = sqlSource;
 5       mappedStatement.statementType = StatementType.PREPARED;
 6       mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
 7       mappedStatement.resultMaps = new ArrayList<ResultMap>();
 8       mappedStatement.sqlCommandType = sqlCommandType;
 9       mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
10       String logId = id;
11       if (configuration.getLogPrefix() != null) {
12         logId = configuration.getLogPrefix() + id;
13       }
14       mappedStatement.statementLog = LogFactory.getLog(logId);
15       mappedStatement.lang = configuration.getDefaultScriptingLanuageInstance();
16     }

如果實在不想用默認的方式進行處理的話,可以在相關每一個XML節點的statementType屬性進行設置。如下

<select id="SelectProducts" resultMap="result" statementType="STATEMENT" >
        select * from Products where #{0} > ProductID and ProductName like #{1}
    </select>

生成Statement接口實例要用到StatementHandler接口的倆個方法:prepare方法和parameterize方法。prepare方法用於完成構建Statement接口實例。parameterize方法用於處理Statement接口實例對應的參數。理解這一過程需要調頭查看SimpleExecutor類的doQuery方法。

 1 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       return handler.<E>query(stmt, resultHandler) ; 
 8     } finally {
 9       closeStatement(stmt);
10     }
11   }

源碼的prepareStatement方法里面可以體現prepare方法和parameterize方法的作用。通過prepareStatement方法就可以得到一個完整Statement接口實例。最后在通過StatementHandler接口實例的query方法來獲得對應的結果。筆者暫且跳過這一個過程(query方法處理結果)。讓我們來看看關於prepare方法和parameterize方法。

 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

上面說到prepare方法就是用於構建Statement接口實例。默認情況是PreparedStatementHandler類。那么筆者就拿PreparedStatementHandler類來切入吧。當筆者點開PreparedStatementHandler類的源碼,試着去查看一下prepare方法。發現找不到。原來他在PreparedStatementHandler類的父類(BaseStatementHandler類)里面。

 1  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
 2     ErrorContext.instance().sql(boundSql.getSql());
 3     Statement statement = null;
 4     try {
 5       statement = instantiateStatement(connection);
 6       setStatementTimeout(statement, transactionTimeout);
 7       setFetchSize(statement);
 8       return statement;
 9     } catch (SQLException e) {
10       closeStatement(statement);
11       throw e;
12     } catch (Exception e) {
13       closeStatement(statement);
14       throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
15     }
16   }

每一個框架都有一個共同的特點——方法調來調去的。prepare方法里面通過instantiateStatement方法來返回相關的Statement實例。而這個方法卻是一個抽象方法。

 protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

他的實例就是在各自的子類里面。完美的利用了繼承的好處。

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

上面的源碼是PreparedStatementHandler類的。所以不用筆者多講——就是生成PreparedStatement實例。

有了PreparedStatement實例,當然就要對他進行設置相應的參數。這也是parameterize方法的作用。但是如何是簡單的設置那顯然沒有什么可說的。主要還是因為mybatis對於設置參數方面做精心的設計。好話不多說。還是看一下源碼最實在。

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

ParameterHandler接口的作用顯然不用筆者多講。DefaultParameterHandler類便是他的實例類。DefaultParameterHandler類的代碼不多,可是他包含的內容卻很多。進去看一下就知道了。

 1  public void setParameters(PreparedStatement ps) {
 2     ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
 3     List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
 4     if (parameterMappings != null) {
 5       for (int i = 0; i < parameterMappings.size(); i++) {
 6         ParameterMapping parameterMapping = parameterMappings.get(i);
 7         if (parameterMapping.getMode() != ParameterMode.OUT) {
 8           Object value;
 9           String propertyName = parameterMapping.getProperty();
10           if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
11             value = boundSql.getAdditionalParameter(propertyName);
12           } else if (parameterObject == null) {
13             value = null;
14           } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
15             value = parameterObject;
16           } else {
17             MetaObject metaObject = configuration.newMetaObject(parameterObject);
18             value = metaObject.getValue(propertyName);
19           }
20           TypeHandler typeHandler = parameterMapping.getTypeHandler();
21           JdbcType jdbcType = parameterMapping.getJdbcType();
22           if (value == null && jdbcType == null) {
23             jdbcType = configuration.getJdbcTypeForNull();
24           }
25           try {
26             typeHandler.setParameter(ps, i + 1, value, jdbcType);
27           } catch (TypeException e) {
28             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
29           } catch (SQLException e) {
30             throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
31           }
32         }
33       }
34     }

BoundSql類又一次出現在我們的面前,前面筆者也沒有提過關於BoundSql類的作用。因為如果沒有一個上下文的作用是很難推斷出BoundSql類。筆者也只是從源碼來看的,也不一定是對的。前面部分的源碼里面有出現過使用MappedStatement類。他可以說是一個包含節點(select節點,update節點等)信息的類。但是對的具體的SQL語句用到的信息卻很少。那么BoundSql類就是存放於的組裝SQL句語信息。從源碼里面我們可以看到BoundSql類處理返回結果的信息卻沒有。有的只是SQL語句的參數之類的信息。如下他的內部成員。

 private String sql;
  private List<ParameterMapping> parameterMappings;
  private Object parameterObject;
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;

有了對BoundSql類的概念認識,我們接着談談上面源碼(setParameters方法部分)里面發生的事情吧。如果想要一下就明白他是做什么的怎么樣子做。那筆者只能說自己功力不行。筆者只能大概的看出他在做什么。通過BoundSql類獲得相應的ParameterMapping類。找到對應的屬性名(如:#{id})。接着通過傳入的參數信息獲得對應的MetaObject類。在通過MetaObject類和屬性名獲得相應屬性名的值。最后一步就是通過TypeHandler接口實例設置值了。

到了這里面StatementHandler接口的工作算是結束了。對於MetaObject類是如何獲得的,他又是什么。筆者這里就不多加言論。筆者留意的點還是TypeHandler接口。這部分的知識點官網也講到過——typeHanlders。了解TypeHandler接口的源碼也就是成了下一個目標了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM