乐趣区

关于mybatis:面试面试官有没有在Mybatis执行过程上为过难你呢看完就不再怂图文解析

前言

在理解了 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代理类生成流程
  • MapperProxy代理类执行操作

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

通过以上的剖析,总结出

  • Mapper接口理论对象为代理对象 MapperProxy
  • MapperProxy 继承 InvocationHandler,实现 invoke 办法;
  • MapperProxyFactory::newInstance() 办法,基于 JDK 动静代理的形式创立了一个 MapperProxy 的代理类;
  • 最终会调用到mapperMethod::execute() 办法执行,实现操作。
  • 而且更重要一点是,MyBatis 应用的动静代理和广泛动静代理有点区别,没有实现类,只有接口,MyBatis 动静代理类图构造如下所示:


已以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

  • BaseExecutor 是一个抽象类,实现了大部分 Executor 接口定义的性能,升高了接口实现的难度。BaseExecutor 基于适配器设计模式之接口适配会有三个子类,别离是 SimpleExecutorReuseExecutorBatchExecutor

    • SimpleExecutor : 是 MyBatis 中 默认 简略执行器,每执行一次 updateselect,就开启一个 Statement 对象,用完立即敞开 Statement 对象
    • ReuseExecutor : 可重用执行器,执行 updateselect,以 sql 作为 key 查找 Statement 对象,存在就应用,不存在就创立,用完后,不敞开 Statement 对象,而是搁置于 Map<String, Statement> 内,供下一次应用。简言之,就是重复使用 Statement 对象
    • BatchExecutor : 批处理执行器,用于执行 update(没有 select,JDBC 批处理不反对 select 将多个 SQL 一次性输入到数据库,
  • CachingExecutor: 缓存执行器,为 Executor 对象减少了 二级缓存 的相干功:先从缓存中查问后果,如果存在就返回之前的后果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 能够是下面任何一个执行器。

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 对象与数据库进行交互类,另外一个是路由性能的,

  • RoutingStatementHandler: 对 Statement 对象没有实际操作,次要负责另外三个 StatementHandler 的创立及调用,而且在 MyBatis 执行时, 应用的 StatementHandler 接口对象实际上就是 RoutingStatementHandler 对象。
  • SimpleStatementHandler: 治理 Statement 对象,用于简略 SQL 的解决。
  • PreparedStatementHandler: 治理 Statement 对象,预处理 SQL 的接口。
  • CallableStatementHandler:治理 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 语句参数动静赋值,其有两个接口

  • getParameterObject:用于读取参数
  • setParameters: 用于对 PreparedStatement 的参数赋值

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 等),其有三接口

  • handleResultSets(): 处理结果集
  • handleCursorResultSets():批量处理结果集
  • handleOutputParameters():解决存储过程返回的后果集

其默认的实现为DefaultResultSetHandler, 次要性能为:

  • 解决Statement 执行后产生的后果集生成绝对的输入后果、
  • 解决存储过程执行后的输入参数

那看看 DefaultResultSetHandler::handleResultSets() 如何解决?

  • 当有多个 ResultSet 的后果汇合,每个 ResultSet 对应一个 Object 对象,如果不思考存储过程,一般的查问只有一个 ResultSet
  • ResultSetWrapper封装了 ResultSet 后果集, 其属性蕴含 ResultSet ,ResultMap
@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 执行查询数据库数据,后果返回集映射等,而且每个环节波及的内容都很多,每个接口都能够抽出独自剖析,后续有工夫再一一具体的看看。前面还是再剖析一下插件的利用。

各位看官还能够吗?喜爱的话,动动手指导个赞???? 呗!!谢谢反对!
欢送扫码关注,原创技术文章第一工夫推出

退出移动版