首先約定文中將的四大對象是指:executor, statementHandler,parameterHandler,resultHandler對象。(為了方便下面的文章說道四大對象就專指它們)
講到statementHandler,毫無疑問它是我們四大對象最重要的一個,它的任務就是和數據庫對話。在它這里會使用parameterHandler和ResultHandler對象為我們綁定SQL參數和組裝最后的結果返回。
一、statementHandler對象的定義:
首先我們先來看看statementHandler接口的定義:
- public interface StatementHandler {
- Statement prepare(Connection connection)
- throws SQLException;
- void parameterize(Statement statement)
- throws SQLException;
- void batch(Statement statement)
- throws SQLException;
- int update(Statement statement)
- throws SQLException;
- <E> List<E> query(Statement statement, ResultHandler resultHandler)
- throws SQLException;
- BoundSql getBoundSql();
- ParameterHandler getParameterHandler();
- }
這里有幾個重要的方法,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方法的實現,
- @Override
- public Statement prepare(Connection connection) throws SQLException {
- ErrorContext.instance().sql(boundSql.getSql());
- Statement statement = null;
- try {
- statement = instantiateStatement(connection);
- setStatementTimeout(statement);
- 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;
顯然我們通過源碼更加關注抽象方法instantiateStatement是做了什么事情。它依舊是一個抽象方法,那么它就有其實現類。那就是之前說的那幾個具體的StatementHandler對象,讓我們看看PreparedStatementHandler:
- @Override
- protected Statement instantiateStatement(Connection connection) throws SQLException {
- String sql = boundSql.getSql();
- if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
- String[] keyColumnNames = mappedStatement.getKeyColumns();
- if (keyColumnNames == null) {
- return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
- } else {
- return connection.prepareStatement(sql, keyColumnNames);
- }
- } else if (mappedStatement.getResultSetType() != null) {
- return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
- } else {
- return connection.prepareStatement(sql);
- }
- }
好這個方法非常簡單,我們可以看到它主要是根據上下文來預編譯SQL,這是我們還沒有設置參數。設置參數的任務是交由,statement接口的parameterize方法來實現的。
3、parameterize方法:
上面我們在prepare方法里面預編譯了SQL。那么我們這個時候希望設置參數。在Statement中我們是使用parameterize方法進行設置參數的。
讓我們看看PreparedStatementHandler中的parameterize方法:
- @Override
- public void parameterize(Statement statement) throws SQLException {
- parameterHandler.setParameters((PreparedStatement) statement);
- }
很顯然這里很簡單是通過parameterHandler來實現的,我們這篇文章只是停留在statementhandler的程度,等我們講解parameterHandler的時候再來看它如何實現吧,期待一下吧。
4、query/update方法
我們用了prepare方法預編譯了SQL,用了parameterize方法設置參數,那么我們接下來肯定是想執行SQL,而SQL無非是兩種:
一種是進行查詢——query,另外就是更新——update。
這些方法都很簡單,讓我們看看PreparedStatementHandler的實現:
- @Override
- public int update(Statement statement) throws SQLException {
- PreparedStatement ps = (PreparedStatement) statement;
- ps.execute();
- int rows = ps.getUpdateCount();
- Object parameterObject = boundSql.getParameterObject();
- KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
- keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
- return rows;
- }
- ......
- @Override
- public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
- PreparedStatement ps = (PreparedStatement) statement;
- ps.execute();
- return resultSetHandler.<E> handleResultSets(ps);
- }
我們可以看到如果是進行update的,它將會執行生成主鍵的操作(插入數據要自動生成主鍵的時候),然后就返回影響行數。
如果是進行query的就更加簡單了,它就是執行SQL語句,然后講結果使用resultHandler的handleResultSets去完成我們的結果組裝。至於resultHandler的內部實現還是很復雜的,值得期待哦。這里我們暫且不講等待下一章吧。
5、總結
StatementHandler是MyBatis四大對象里面最重要的對象,它的方法是十分重要的,也是我們插件的基礎。
當我們需要改變sql的時候,顯然我們要在預編譯SQL(prepare方法前加入修改的邏輯)。
當我們需要修改參數的時候我們可以在調用parameterize方法前修改邏輯。或者使用ParameterHandler來改造設置參數。
我們需要控制組裝結果集的時候,也可以在query方法前后加入邏輯,或者使用ResultHandler來改造組裝結果。
懂的這些方法,才能理解我需要攔截什么對象,如何處理插件,這是MyBatis的核心內容。