Mybatis源碼閱讀(四):核心接口4.1——StatementHandler

前言

難得復工了,公司百業待興,有一大堆接口需要對接,忙的不行。回過神來發現自己快一個月沒寫博客了,趕緊抽時間寫一寫,不能斷更。

截止上一篇博客,我們已經把結果集映射的內容介紹完畢,接下來就是對Mybatis中的核心接口進行介紹,通過介紹這些核心接口,使讀者們更深刻地理解Mybatis的運行機制以及原理。

StatementHandler

StatementHandler接口是Mybatis的核心接口之一,它完成了Mybatis中最核心的工作,也是Executor接口實現的基礎。

StatementHandler接口中功能有很多,如創建Statement對象、執行SQL語句、批量執行SQL語句等。StatementHandler接口定義如下。

/**
 * Mybatis核心接口之一,完成了Mybatis中最核心的工作,也是Executor接口實現的基礎。
 *
 * @author Clinton Begin
 */
public interface StatementHandler {

    /**
     * 從數據庫連接中獲取一個Statement
     *
     * @param connection
     * @param transactionTimeout
     * @return
     * @throws SQLException
     */
    Statement prepare(Connection connection, Integer transactionTimeout)
            throws SQLException;

    /**
     * 綁定statement執行時所需的實參
     *
     * @param statement
     * @throws SQLException
     */
    void parameterize(Statement statement)
            throws SQLException;

    /**
     * 批量執行sql語句
     *
     * @param statement
     * @throws SQLException
     */
    void batch(Statement statement)
            throws SQLException;

    /**
     * 執行 update/insert/delete語句
     *
     * @param statement
     * @return
     * @throws SQLException
     */
    int update(Statement statement)
            throws SQLException;

    /**
     * 執行select語句
     *
     * @param statement
     * @param resultHandler
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> List<E> query(Statement statement, ResultHandler resultHandler)
            throws SQLException;

    /**
     * 查詢遊標
     *
     * @param statement
     * @param <E>
     * @return
     * @throws SQLException
     */
    <E> Cursor<E> queryCursor(Statement statement)
            throws SQLException;

    /**
     * 獲取BoundSql
     *
     * @return
     */
    BoundSql getBoundSql();

    /**
     * 獲取ParameterHandler
     *
     * @return
     */
    ParameterHandler getParameterHandler();

}

該接口的繼承關係如下。其中,CallableStatementHandler用於調用存儲過程,而mysql的存儲過程在大多數公司都很少使用甚至禁止使用,這裏就不對其進行介紹了,有興趣的朋友可以自己參考源碼閱讀

RoutingStatementHandler

RoutingStatementHandler使用策略模式,根據MappedStatement中指定的statementType字段,創建對應的StatementHandler接口實現。由於其這種思路,還有人認爲這是個路由。RoutingStatementHandler核心代碼如下。

public class RoutingStatementHandler implements StatementHandler {

    /**
     * 底層封裝的真正的StatementHandler對象
     * 這裏的delegate在寫框架的過程中使用較多
     */
    private final StatementHandler delegate;

    public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // RoutingStatementHandler的主要功能是根據MappedStatement的配置,生成對應的StatementHandler對象
        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());
        }

    }

}

BaseStatementHandler

BaseStatementHandler是一個抽象類,實現了StatementHandler接口。它只提供了一些參數綁定相關的方法,對數據庫不進行任何操作。該類的字段如下。

    protected final Configuration configuration;
    protected final ObjectFactory objectFactory;
    protected final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * 記錄結果集映射對象
     */
    protected final ResultSetHandler resultSetHandler;

    /**
     * 記錄使用的參數處理器對象。ParameterHandler的主要功能是爲SQL綁定實參
     * 也就是使用傳入的實參替換SQL語句中的 ? 佔位符
     */
    protected final ParameterHandler parameterHandler;

    protected final Executor executor;

    /**
     * 記錄SQL語句對應的MappedStatement
     */
    protected final MappedStatement mappedStatement;

    /**
     * 記錄用戶設置的offset和limit,用於在結果集中定位映射的起始位置和結束位置
     */
    protected final RowBounds rowBounds;

    protected BoundSql boundSql;

在BaseStatementHandler的構造方法中,除對上面的字段進行初始化之外,還會調用KeyGenerator.processBefore()方法初始化SQL的主鍵,具體實現如下。

    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) {
            // 調用KeyGenerator.processBefore 方法獲取主鍵
            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);
    }

    protected void generateKeys(Object parameter) {
        KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
        ErrorContext.instance().store();
        keyGenerator.processBefore(executor, mappedStatement, null, parameter);
        ErrorContext.instance().recall();
    }

BaseStatementHandler實現了StatementHandler的prepare方法,該方法用來初始化Statement對象,然後爲其分配超時時間等屬性。其中,初始化StatementHandler的方法是個抽象方法instantiateStatement,是一個抽象方法,需要由子類去實現,代碼如下。

    @Override
    public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
        ErrorContext.instance().sql(boundSql.getSql());
        Statement statement = null;
        try {
            // 初始化statement對象,交給子類去實現
            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);
        }
    }

BaseStatementHandler依賴兩個重要的組件,分別是ParameterHandler和ResultSetHandler。後者在前面的文章中已經介紹,不在重複。

ParameterHandler

通過前面介紹動態SQL可知,在BoundSql中記錄的SQL語句可能包含?佔位符,每個?佔位符都對應了BoundSql.parameterMapings集合中的一個元素。在ParameterHandler中只定義了一個setParameter方法,該方法用於爲SQL語句綁定實參。ParameterHandler接口只有唯一一個實現類 DefaultParameterHandler,核心字段如下。

    /**
     * 管理Mybatis中的全部TypeHandler對象
     */
    private final TypeHandlerRegistry typeHandlerRegistry;

    /**
     * SQL節點
     */
    private final MappedStatement mappedStatement;

    /**
     * 用戶傳入的實參對象
     */
    private final Object parameterObject;
    private final BoundSql boundSql;
    private final Configuration configuration;

DefaultParameterHandler的setParameters方法中會遍歷parameterMappings集合中記錄的ParameterMapping對象,並根據其中記錄的參數名稱找到對應的實參,再與SQL綁定。setParameters方法如下。

    /**
     * 設置參數
     * @param ps
     */
    @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);
                // OUT是存儲過程中的輸出參數,這裏需要過濾掉這些參數
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    // 獲取參數名稱
                    String propertyName = parameterMapping.getProperty();
                    // 獲取對應的實參值
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        // 實參可以直接通過TypeHandler轉換成jdbcType
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    // 獲取parameterMapping中設置的TypeHandler對象
                    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 | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
    }

對SQL語句綁定完實參後,就可以調用Statement對象的execute方法執行SQL了。

SimpleStatementHandler

SimpleStatementHandler是BaseStatementHandler的子類,底層使用了Statement對象完成數據庫的相關操作,所以SQL語句中不能存在佔位符,因此parameterize方法是空實現。

SimpleStatementHandler的instantiateStatement方法直接通過JDBC Connection創建Statement,代碼如下。

    /**
     * 創建statement對象
     * @param connection
     * @return
     * @throws SQLException
     */
    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
            return connection.createStatement();
        } else {
            return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }

query方法完成了數據庫查詢的操作,並通過ResultSetHandler將結果集映射成結果對象,代碼如下。

    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        String sql = boundSql.getSql();
        statement.execute(sql);
        return resultSetHandler.handleResultSets(statement);
    }

update方法負責執行insert、update、delete的SQL語句,並根據配置的KeyGenerator獲取數據庫生成的主鍵,具體實現如下。

    /**
     * 負責執行insert、update、delete語句
     * @param statement
     * @return
     * @throws SQLException
     */
    @Override
    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;
    }

PreparedStatementHandler

該類底層依賴於PrepareStatement對象完成數據庫的操作,instantiateStatement方法直接調用Connection的prepareStatement方法創建PrepareStatement對象,代碼如下。

    /**
     * 直接調用Connection的prepareStatement方法創建PrepareStatement對象
     * @param connection
     * @return
     * @throws SQLException
     */
    @Override
    protected Statement instantiateStatement(Connection connection) throws SQLException {
        // 獲取待執行的sql
        String sql = boundSql.getSql();
        // 根據keyGenerator的值創建PrepareStatement對象
        if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
            String[] keyColumnNames = mappedStatement.getKeyColumns();
            if (keyColumnNames == null) {
                // 返回數據庫生成的主鍵
                return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
            } else {
                // 在insert語句執行完成之後,將keyColumnNames指定的列返回
                return connection.prepareStatement(sql, keyColumnNames);
            }
        } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
            // 設置結果集是否可以滾動以及遊標是否可以上下移動,設置結果集是否可更新
            return connection.prepareStatement(sql);
        } else {
            // 創建普通的PrepareStatement對象
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }

PrepareStatement的其他方法的實現與SimpleStatementHandler對應的方法實現類型,這裏就不贅述了。

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