美文网首页
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