美文网首页
BaseExecutor.query()方法解析

BaseExecutor.query()方法解析

作者: 布拉德老瓜 | 来源:发表于2021-03-13 23:59 被阅读0次

    BaseExecutor.query(ms, ... , boundSql)方法执行流程

    query()

    它的逻辑很简单:

    • 尝试从缓存获取,获取不到或不具备从缓存获取条件(即resultHandler != null),则从数据库中获取
    • 方法的核心在于queryFromeDataBase(ms, parameter, rowBounds, resultHandler, key, boundSql)。
    • 查完之后放入本地缓存

    queryFromeDataBase(ms, param, ... ,cacheKey, boundSql)

    这个方法目前没什么好说的,且看doQuery(...)方法吧

        private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
            this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
    
            List list;
            try {
                //todo:重点关注
                list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
            } finally {
                this.localCache.removeObject(key);
            }
            // 放到缓存
            this.localCache.putObject(key, list);
            // CALLABLE的sql语句, 作为存放到参数缓存。也就是说,该语句的执行结果可以作为参数,被其他的语句使用。
            if (ms.getStatementType() == StatementType.CALLABLE) {
                this.localOutputParameterCache.putObject(key, parameter);
            }
    
            return list;
        }
    

    queryFromeDataBase(ms, ... , boundSql)的核心在于doQuery(ms, ... , boundSql),重点来了。

    可以看到这个方法的就做了四件事:

    1. 创建StatementHandler
    2. 通过statementHandler解析出statement
    3. 利用statementHandler完成查询
    4. closeStatement.
      其主体部分都离不开StatementHandler, 因此可以说,搞明白了StatementHandler , 也就搞明白了doQuery.我们知道StatementHandler、ParameterHandler、ResultSetHandler和Executor是mybatis的四大核心对象,所以在这里应当留心StatementHandler是如何被创建的、内部有哪些重要属性,以及它有哪些作用。
        public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
            Statement stmt = null;
    
            List var9;
            try {
                Configuration configuration = ms.getConfiguration();
                StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                stmt = this.prepareStatement(handler, ms.getStatementLog());
                var9 = handler.query(stmt, resultHandler);
            } finally {
                this.closeStatement(stmt);
            }
    
            return var9;
        }
    

    1. 方法创建statementHandler时的参数包含了哪些信息,可以让我们与数据库交互并查到数据

        configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql)
    
      1. this.wrapper: 注意,我们虽然在这里执行的是父类的实例方法,但当前对象是simpleExecutor.前面提到过,cachingExecutor的delegate属性是当前的simpleExecutor对象,而当前的simpleExecutor对象的wrapper则是这个cachingExecutor。executor内封装了执行该sql所需的事务信息transaction,transaction内部又包含了数据库连接信息和事务是否自动提交、事务隔离级别,如dataSource。所以通过wrapper可以来完成数据库访问和事务控制。


        当前系统中cachingExecurot与simpleExecutor的关系
    debug: this
      1. MappedStatement: ms中有哪些值得关注的属性呢
      • statementType: STATEMENT| PREPARED | CALLABLE. 分别对应原生jdbc中的stmt、preparedStmt和CallableStatement。前两个好理解,CallableStatement主要是调用数据库中的存储过程,接收存储过程的返回值。存储过程case
      1. rowBounds: rowBounds, 分页参数,包含offset和limit两个属性


        rowBounds
      1. ResultHandler: resultHandler
      1. BoundSql: boundSql
      • sql: 将#{}或${}替换成"?"之后的sql语句
      • parameterMappings: ParameterMapping类型的数组,他包含对sql语句中参数的描述信息,如参数名,javaType, jdbcType等信息,但是并不包含参数的取值。参数在sql中的位置与在parameterMappings中的位置是一致的。
      • parameterObject: parameterMap. mapperMethod将方法的参数解析完成之后,以paramName: paramValue的形式存放在了该paramMap中。


        boundSql属性

    总之,数据库连接,事务控制,执行sql所需的语句,sql中参数的位置、名称和取值信息现在都有了。

    2. StatementHandler的创建过程

    • 首先创建一个简单的RoutingStatementhandler
    • 再通过拦截器链为他添加插件,与对simpleExecutor的添加插件原理是一样的:迭代每一个插件,为当前的target创建代理对象,并将代理对象重新赋值给target.
        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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
            return statementHandler;
        }
    
    
    创建RoutingStatementhandler

    判断当前statement的类类型,调用对应的构造方法,将结果作为当前RoutingStatementhandler的代理。此后实际干活的都是这个delegate.

        public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
            switch(ms.getStatementType()) {
            case STATEMENT:
                this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case PREPARED:
                this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            case CALLABLE:
                this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                break;
            default:
                throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
            }
        }
    

    这三种statementHandler从构造方法上来讲都是一样的,只是最终创建出来的handler类型不一样。

    public class PreparedStatementHandler extends BaseStatementHandler {
        public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
            super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
        }
    ...
    }
    

    statementHandler的构造方法都是调父类构造方法super(xxxxx)。

        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 = this.configuration.getTypeHandlerRegistry();
            this.objectFactory = this.configuration.getObjectFactory();
            if (boundSql == null) {
                this.generateKeys(parameterObject);
                boundSql = mappedStatement.getBoundSql(parameterObject);
            }
    
            this.boundSql = boundSql;
            this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
            this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);
        }
    

    创建该对象过程中重要的点在于parameterHandler和resultSetHandler.(他们也属于四个核心对象,这下核心对象就全部创建完了)。ParameterHandler负责为 PreparedStatement 的 sql 语句参数动态赋值。 ResultSetHandler负责处理两件事:(1)处理Statement执行后产生的结果集,生成结果列表(2)处理存储过程执行后的输出参数。关于这两个组件,等有时间另外拧出来讲。

    • 在创建完stmtHandler之后,就要为他添加插件了。
      添加插件pluginAll伪代码
    //pluginAll伪代码
    foreach interceptor in chain{
      target = interceptor.plugin(target);
    }
    return target;
    

    interceptor.plugin(target)创建代理对象

        // interceptor.plugin(target)
        default Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        public static Object wrap(Object target, Interceptor interceptor) {
            Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
            Class<?> type = target.getClass();
            Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
            return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
        }
    
    • statementHandler内部结构
      如图:


      statementHandler
    • statementHandler有什么作用?query(stmt, resultHandler)是如何工作的?
      StatementHandler 是四大组件中最重要的一个对象,负责操作 Statement 对象与数据库进行交流,在工作时还会使用 ParameterHandler 和 ResultSetHandler 对参数进行映射,对结果进行实体类的绑定。这句话总感觉干巴巴的,为什么呢?且先不说ParameterHandler 和 ResultSetHandler。问几个问题:Statement对象是谁啊?statementHandler凭什么能操作它与跟数据库的交流?交流的过程是什么?
      在上面说executor.doQuery()的时候,有这样一段代码来完成数据的查询。它创建了StatementHandler,利用handler创建了Statement对象,最后由handler去根据该statement完成数据的查询。

    2. Statement的创建过程

    重新post一份doQuery代码, 现在我们完成了StatementHandler的创建。

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
            Statement stmt = null;
    
            List var9;
            try {
                Configuration configuration = ms.getConfiguration();
                // here we are
                StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
                // the next thing to do
                stmt = this.prepareStatement(handler, ms.getStatementLog());
                var9 = handler.query(stmt, resultHandler);
            } finally {
                this.closeStatement(stmt);
            }
    
            return var9;
        }
    

    接下来就要解析sql语句了,之前的sql语句信息在boundSql中。我们来看看如何解析吧。

        //executor.prepareStatement()
        private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
            // 获取连接对象
            Connection connection = this.getConnection(statementLog);
            // 创建statement对象 
            Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
            // statement对象参数化
            handler.parameterize(stmt);
            return stmt;
        }
    
    • 1.获取连接
      之前讲过,数据库连接信息封装在了transaction中,包括connection和dataSource.那么如果transaction.connection != null,也就是创建了,就直接返回该对象。如果transaction内的connection对象还没创建,那就创建一个connection对象。

    executor.getConnection()

        protected Connection getConnection(Log statementLog) throws SQLException {
            Connection connection = this.transaction.getConnection();
            return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
        }
    

    transaction.getConnection(): 如果connection已经创建直接返回该对象,没创建则利用dataSource去创建一个,然后设置隔离级别和是否自动提交。

        public Connection getConnection() throws SQLException {
            if (this.connection == null) {
                this.openConnection();
            }
    
            return this.connection;
        }
    
        protected void openConnection() throws SQLException {
            if (log.isDebugEnabled()) {
                log.debug("Opening JDBC Connection");
            }
    
            this.connection = this.dataSource.getConnection();
            if (this.level != null) {
                this.connection.setTransactionIsolation(this.level.getLevel());
            }
    
            this.setDesiredAutoCommit(this.autoCommit);
        }
    
      1. 创建Statement
        核心在于instantiateStatement
        public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
            ErrorContext.instance().sql(this.boundSql.getSql());
            Statement statement = null;
    
            try {
                 // Look here
                statement = this.instantiateStatement(connection);
                this.setStatementTimeout(statement, transactionTimeout);
                this.setFetchSize(statement);
                return statement;
            } catch (SQLException var5) {
                this.closeStatement(statement);
                throw var5;
            } catch (Exception var6) {
                this.closeStatement(statement);
                throw new ExecutorException("Error preparing statement.  Cause: " + var6, var6);
            }
        }
    
        // BaseStatementHandler中的该方法为抽象方法,由其子类实现
        protected abstract Statement instantiateStatement(Connection var1) throws SQLException;
    
    

    BaseStatementHandler中的instantiateStatement为抽象方法,咱现在用的是他的子类PreparedStatementHandler,来看看是如何实现的吧。

        //PreparedStatementHandler.instantiateStatement(connection)
        protected Statement instantiateStatement(Connection connection) throws SQLException {
            String sql = this.boundSql.getSql();
            if (this.mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
                String[] keyColumnNames = this.mappedStatement.getKeyColumns();
                return keyColumnNames == null ? connection.prepareStatement(sql, 1) : connection.prepareStatement(sql, keyColumnNames);
            } else {
                return this.mappedStatement.getResultSetType() == ResultSetType.DEFAULT ? connection.prepareStatement(sql) : connection.prepareStatement(sql, this.mappedStatement.getResultSetType().getValue(), 1007);
            }
        }
    

    从boundSql中获取其sql属性,然后connection.prepareStatement(...)

        public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
            ErrorContext.instance().sql(this.boundSql.getSql());
            Statement statement = null;
    
            try {
                statement = this.instantiateStatement(connection);
                this.setStatementTimeout(statement, transactionTimeout);
                this.setFetchSize(statement);
                return statement;
            } catch (SQLException var5) {
                this.closeStatement(statement);
                throw var5;
            } catch (Exception var6) {
                this.closeStatement(statement);
                throw new ExecutorException("Error preparing statement.  Cause: " + var6, var6);
            }
        }
    
    
    

    经过这一步操作,statement对象就实例化了,有了数据库连接的connection和sql语句信息,但是sql语句还是没有参数化。

    • 3.statement参数化
        public void setParameters(PreparedStatement ps) {
            ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
            List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
            if (parameterMappings != null) {
                for(int i = 0; i < parameterMappings.size(); ++i) {
                    ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        String propertyName = parameterMapping.getProperty();
                        Object value;
                        if (this.boundSql.hasAdditionalParameter(propertyName)) {
                            value = this.boundSql.getAdditionalParameter(propertyName);
                        } else if (this.parameterObject == null) {
                            value = null;
                        } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                            value = this.parameterObject;
                        } else {
                            MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                            value = metaObject.getValue(propertyName);
                        }
    
                        TypeHandler typeHandler = parameterMapping.getTypeHandler();
                        JdbcType jdbcType = parameterMapping.getJdbcType();
                        if (value == null && jdbcType == null) {
                            jdbcType = this.configuration.getJdbcTypeForNull();
                        }
    
                        try {
                            typeHandler.setParameter(ps, i + 1, value, jdbcType);
                        } catch (SQLException | TypeException var10) {
                            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                        }
                    }
                }
            }
    

    参数化的过程简单来说就是遍历parameterMappings数组,数组中第i个元素对应的就是sql语句中第i个参数的信息(不包含参数值),从中获取property(即参数名)。再根据参数名去boundSql的parameterObject(本质是一个map)中获取该参数名对应的参数值。最后,将该参数值拼接到sql语句后面的对应位置。参数化完成之后的statement:

    com.mysql.cj.jdbc.ClientPreparedStatement: select * from article_share_info where article_id = 2
    

    查询过程handler.query(stmt, resultHandler)

    将statement转为preparedStatement, 再由它执行,最后由ResultSetHandler完成结果集的封装。

        public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
            //转ps
            PreparedStatement ps = (PreparedStatement)statement;
            //执行sql语句并将结果以字节数组的形式存放在ps.results中
            ps.execute();
            // ResultSetHandler解析ps内的结果集,封装结果集为指定的resultMap类型并将其返回
            return this.resultSetHandler.handleResultSets(ps);
        }
    

    相关文章

      网友评论

          本文标题:BaseExecutor.query()方法解析

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