程序员

面试被问Mybatis执行过程问题不知所措?熟记这六点,从此不再

2020-09-27  本文已影响0人  风平浪静如码

前言

在了解了MyBatis初始化加载过程后,我们也应该研究看看SQL执行过程是怎样执行?这样我们对于Mybatis的整个执行流程都熟悉了,在开发遇到问题也可以很快定位到问题。

更重要的,在面试中遇到面试官咨询Mybatis的知识点的时候,可以很顺畅的把这一套流程讲出来,面试官也会觉得你已掌握Mybatis知识点了,可能就不问了。赶紧瞄瞄

简介SQL执行过程

经过MyBatis初始化加载Sql执行过程所需的信息后,我们就可以通过 SqlSessionFactory 对象得到 SqlSession ,然后执行 SQL 语句了,接下来看看Sql执行具体过程,SQL大致执行流程图如下所示:

接下来我们来看看每个执行链路中的具体执行过程,

SqlSession

SqlSession 是 MyBatis 暴露给外部使用的统一接口层,通过 SqlSessionFactory 创建,且其是包含和数据库打交道所有操作接口。

下面通过时序图描述 SqlSession 对象的创建流程:

在生成SqlSession的同时,基于executorType初始化好Executor 实现类。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

最顶层的SqlSession接口已生成,那我们可以来看看sql的执行过程下一步是怎样的呢?怎样使用代理类MapperProxy

MapperProxy

MapperProxyMapper接口与SQL 语句映射的关键,通过 MapperProxy 可以让对应的 SQL 语句跟接口进行绑定的,具体流程如下:

MapperProxy代理类生成流程

其中,MapperRegistryConfiguration 的一个属性,在解析配置时候会在MapperRegistry 中缓存了 MapperProxyFactoryknownMappers 变量Map 集合。

``MapperRegistry会根据mapper接口类型获取已缓存的MapperProxyFactoryMapperProxyFactory会基于SqlSession来生成MapperProxy`代理对象,

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

当调用SqlSession接口时,MapperProxy怎么是实现的呢?MyBatisMapper接口 是通过动态代理实现的,调用 Mapper 接口的任何方法都会执行 MapperProxy::invoke() 方法,

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            //Object类型执行
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            //接口默认方法执行
            if (method.isDefault()) {
                if (privateLookupInMethod == null) {
                    return this.invokeDefaultMethodJava8(proxy, method, args);
                }

                return this.invokeDefaultMethodJava9(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

但最终会调用到mapperMethod::execute() 方法执行,主要是判断是 INSERTUPDATEDELETESELECT 语句去操作,其中如果是查询的话,还会判断返回值的类型。

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

通过以上的分析,总结出

已以SELECT 为例, 调用会SqlSession ::selectOne() 方法。继续往下执行,会执行 Executor::query() 方法。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

执行到Executor类,那么我们来看看其究竟有什么?

Executor

Executor对象为SQL 的执行引擎,负责增删改查的具体操作,顶层接口SqlSession中都会有一个 Executor 对象,可以理解为 JDBC 中 Statement 的封装版。

Executor 是最顶层的是执行器,它有两个实现类,分别是BaseExecutorCachingExecutor

Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。

看完Exector简介之后,继续跟踪执行流程链路分析,SqlSession 中的 JDBC 操作部分最终都会委派给 Exector 实现,Executor::query()方法,看看在Exector的执行是怎样的?

每次查询都会先经过CachingExecutor缓存执行器, 会先判断二级缓存中是否存在查询 SQL ,如果存在直接从二级缓存中获取,不存在即为第一次执行,会直接执行SQL 语句,并创建缓存,都是由CachingExecutor::query()操作完成的。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        //获取查询语句对应的二级缓存
        Cache cache = ms.getCache();
        //sql查询是否存在在二级缓存中
        if (cache != null) {
            //根据 <select> 节点的配置,判断否需要清空二级缓存
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, boundSql);
                //查询二级缓存
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    //二级缓存没用相应的结果对象,调用封装的Executor对象的 query() 方法
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    //将查询结果保存到二级缓存中
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }
        //没有启动二级缓存,直接调用底层 Executor 执行数据数据库查询操作
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

如果在经过CachingExecutor缓存执行器(二级缓存)没有返回值的话,就会执行BaseExecutor 以及其的实现类,默认为SimpleExecutor ,首先会在一级缓存中获取查询结果,获得不到,最终会通过SimpleExecutor:: ()去数据库中查询。

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            //是否清除本地缓存
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                //从一级缓存中,获取查询结果
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                //获取到结果,则进行处理
                if (list != null) {
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    //获得不到,则从数据库中查询
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                //执行延迟加载
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

那么SimpleExecutor::doQuery()如何去数据库中查询获取到结果呢?其实执行到这边mybatis的执行过程就从 Executor转交给 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;
    }

这样我们的执行链路分析已到StatementHandler了,现在让我们去一探究竟其原理

StatementHandler

StatementHandler负责处理Mybatis与JDBC之间Statement的交互,即Statement 对象与数据库进行交互,其为顶级接口,有4个实现类,其中三个是Statement 对象与数据库进行交互类, 另外一个是路由功能的,

在经历过Executor后,基于初始化加载到MapperState中的StatementType的类型通过Configuration.newStatementHandler()方法中的RoutingStatementHandler 生成StatementHandler实际处理类。

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

    }

现在先以PreparedStatementHandler预处理为例,接着Sql的执行链路来分析,StatementHandler::query()StatementHandler::execute()真正执行Sql查询操作。

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

但执行真正查询操作之前,还进行哪些处理呢?还会进行ParameterHandler对 SQL 参数的预处理:对参数进行动态Sql映射,那么ParameterHandler又如何实现对参数进行动态映射的呢?

ParameterHandler

ParameterHandler 参数处理器, 用来设置参数规则的,负责为sql 语句参数动态赋值,其有两个接口

SimpleExecutor执行构造PreparedStatementHandler完,会调用parameterize()方法将PreparedStatement对象里SQL转交ParameterHandler实现类 DefaultParameterHandler::setParameters()方法 设置 PreparedStatement 的占位符参数 。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    //参数动态赋值
    handler.parameterize(stmt);
    return stmt;
  }

DefaultParameterHandler::setParameters()如何对SQL进行动态赋值呢?在执行前将已装载好的BoundSql对象信息进行使用

 public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
     //获取待动态赋值参数列表的封装parameterMappings
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
          //是否为输入参数
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          //获取待动态参数属性名
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          //// 在通过 SqlSource 的parse 方法得到parameterMappings 的具体实现中,我们会得到parameterMappings 的 typeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          //获取jdbc数据类型
          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 参数的预处理,当StatementHandler::execute()真正执行查询操作执行完后,有返回结果,需要对返回结果进行ResultSetHandler处理,现在看看最后的结果的处理流程。

ResultSetHandler

ResultSetHandler 结果解析器,将查询结果的ResultSet 转换成映射的对应结果(java DTO等),其有三接口

其默认的实现为DefaultResultSetHandler,主要功能为:

那看看DefaultResultSetHandler::handleResultSets()如何处理?

@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    //当有多个ResultSet的结果集合,每个ResultSet对应一个Object 对象
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //获得首个 ResultSet 对象,并封装成 ResultSetWrapper 对象
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    //获得 ResultMap 数组
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount); // <3.1> 校验
    while (rsw != null && resultMapCount > resultSetCount) {
        //获得 ResultMap 对象
        ResultMap resultMap = resultMaps.get(resultSetCount);
        //处理 ResultSet ,将结果添加到 multipleResults 中
        handleResultSet(rsw, resultMap, multipleResults, null);
        //获得下一个 ResultSet 对象,并封装成 ResultSetWrapper 对象
        rsw = getNextResultSet(stmt);
        //清理
        cleanUpAfterHandlingResultSet();
        // resultSetCount ++
        resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
        while (rsw != null && resultSetCount < resultSets.length) {
            ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
            if (parentMapping != null) {
                String nestedResultMapId = parentMapping.getNestedResultMapId();
                ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
                handleResultSet(rsw, resultMap, null, parentMapping);
            }
            rsw = getNextResultSet(stmt);
            cleanUpAfterHandlingResultSet();
            resultSetCount++;
        }
    }

    //如果是 multipleResults 单元素,则取首元素返回
    return ollapseSingleResultList(multipleResults);
}

其实在ResultSetHandler结果集处理是比较复杂的,这里只是简单的介绍一下,有兴趣的可以再深入研究一下,后期有空也会写。

执行到这边,Mybatis SQL执行基本完了,会把转换后的结果集返回到操作者。

结论

在SQL执行过程主要涉及了SqlSessionMapperProxy,Executor,StatementHandler,ParameterHandler以及ResultSetHandler,包括参数动态绑定,Sql执行查询数据库数据,结果返回集映射等,而且每个环节涉及的内容都很多,每个接口都可以抽出单独分析,后续有时间再一一详细的看看。后面还是再分析一下插件的应用。

上一篇下一篇

猜你喜欢

热点阅读