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接口的源碼也就是成了下一個目標了。