美文网首页Mybatis随笔
Mybatis随笔(九) StatementHandler解析

Mybatis随笔(九) StatementHandler解析

作者: sunyelw | 来源:发表于2020-03-29 18:46 被阅读0次

    Mybatis从SqlSession到Executor再到Statement,这就是一条SQL执行的调用过程,而Statement接口就是数据库底层的统一对外接口,不同数据库厂商自定义的驱动中就包括实现这个接口。

    而Mybatis并没有直接从Executor访问Statement,中间还套了一层StatementHandler。


    1、StatementHandler概览

    StatementHandler

    跟Executor的继承实现很像,都有一个Base,Base下面又有几个具体实现子类,这又是模板模式的应用。看下另一个Routing就不同于CacheExecutor用于二级缓存之类的实际作用了,仅用于维护三个Base子类的创建与调用。

    • BaseStatementHandler

      • SimpleStatementHandler:JDBC中的Statement接口,处理简单SQL的

      • CallableStatementHandler:JDBC中的PreparedStatement,预编译SQL的接口

      • PreparedStatementHandler:JDBC中的CallableStatement,用于执行存储过程相关的接口

    • RoutingStatementHandler:路由三个Base子类,负责其创建及调用

    看下BaseStatementHandler的属性

    // 1.配置
    protected final Configuration configuration;
    // 2.对象处理工厂
    protected final ObjectFactory objectFactory;
    // 3.类型处理
    protected final TypeHandlerRegistry typeHandlerRegistry;
    // 4.结果集处理
    protected final ResultSetHandler resultSetHandler;
    // 5.参数处理
    protected final ParameterHandler parameterHandler;
    
    // 6.具体执行器
    protected final Executor executor;
    // 7.具体SQL映射对象
    protected final MappedStatement mappedStatement;
    // 8.执行SQL条数参数
    protected final RowBounds rowBounds;
    
    // 9.具体SQL信息
    protected BoundSql boundSql;
    

    2、StatementHandler解析

    因为涉及到了具体操作数据库,就是由三个BaseExecutor的子类自己实现,不过三个的逻辑大体一致,分几个步骤

    2.1 创建StatementHandler
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;
        try {
            Configuration configuration = ms.getConfiguration();
            // here
            // here
            // here
            // 创建 StatementHandler
            StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            stmt = prepareStatement(handler, ms.getStatementLog());
        return handler.query(stmt, resultHandler);
        } finally {
            closeStatement(stmt);
        }
    }
    

    看下 Configuration # newStatementHandler 方法

    public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 1.创建StatementHandler
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 2.插件处理
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
    }
    
    • 创建StatementHandler
    • 插件处理(责任链模式)

    看下 RoutingStatementHandler 的构造方法是如何创建StatementHandler的

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

    嗯,很眼熟的策略模式,按照statementType的值来决定返回哪种StatementHandler。

    statementType的取值?构造方法中默认取值为PREPARED

    mappedStatement.statementType = StatementType.PREPARED;
    

    可以通过<select />的statementType属性指定

    <select id="getAll" resultType="Student2" statementType="CALLABLE">
        SELECT * FROM Student
    </select>
    

    或通过@SelectKey的statementType属性指定

    @SelectKey(keyProperty = "account", 
            before = false, 
            statementType = StatementType.STATEMENT, 
            statement = "select * from account where id = #{id}", 
            resultType = Account.class)
    Account selectByPrimaryKey(@Param("id") Integer id);
    
    2.2 创建Statement

    然后就是创建并处理Statement,用于与JDBC交互了,三个Executor的逻辑基本一致,分三步

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 1.拿到连接
        Connection connection = getConnection(statementLog);
        // 2.创建Statement对象
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 3.预编译
        handler.parameterize(stmt);
        return stmt;
    }
    
    • 拿到连接
    • 创建Statement对象(ReuseExecutor会先去缓存中查看是否存在,不存在再创建)
    • 预编译

    然后是SimpleExecutor、ReuseExecutor就是直接执行了,而BatchExecutor就得存起来等一次批量执行的调用了。

    BaseStatementHandler抽象类只有一个抽象方法 instantiateStatement 方法,是在预编译的时候调用,用于获取Statement对象,由子类实现

    protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
    

    SimpleStatementHandler、CallableStatementHandler比较简单直接获取

    @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);
        }
    }
    
    • 直接通过createStatement拿到Statement对象

    而PrepareStatementHandler则比较复杂

    @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() == ResultSetType.DEFAULT) {
            return connection.prepareStatement(sql);
        } else {
            return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
        }
    }
    
    • 通过prepareStatement预编译方法拿到PrepareStatement

    下面以查询为例简单看下三个Statement的区别。

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

    对于SimpleStatementHandler来说,就是简单执行SQL后,将结果集转化成list返回。

    2.4 PreparedStatementHandler
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        PreparedStatement ps = (PreparedStatement) statement;
        ps.execute();
        return resultSetHandler.handleResultSets(ps);
    }
    

    将Statement转化为PreparedStatement后执行,将结果集转化成list返回。

    2.5 CallableStatementHandler
    @Override
    public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
        CallableStatement cs = (CallableStatement) statement;
        cs.execute();
        List<E> resultList = resultSetHandler.handleResultSets(cs);
        resultSetHandler.handleOutputParameters(cs);
        return resultList;
    }
    

    将Statement转化为CallableStatement后进行操作,处理存储过程的输出并将结果集转化成list返回。

    2.6 ResultSetHandler

    简单看下结果集处理接口ResultSetHandler

    public interface ResultSetHandler {
    
        // 将结果集转化成list
        <E> List<E> handleResultSets(Statement stmt) throws SQLException;
    
        // 将结果集转化出呢个cursor
        <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
    
        // 处理存储过程的输出
        void handleOutputParameters(CallableStatement cs) throws SQLException;
    }
    

    这个接口只有一个默认实现DefaultResultSetHandler,这个类的方法着实有点多。。


    DefaultResultSetHandler-method

    嗯,有需要再来深究。

    总结

    1. Mybatis通过StatementHandler来处理与JDBC的交互
    2. Statement是具体与数据库底层打交道的接口,不同数据库厂商需要自己定义实现,比如MySQL、Oracle等

    相关文章

      网友评论

        本文标题:Mybatis随笔(九) StatementHandler解析

        本文链接:https://www.haomeiwen.com/subject/usuuuhtx.html