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

4次阅读

共计 14738 个字符,预计需要花费 37 分钟才能阅读完成。

前言

在理解了 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 执行查询数据库数据,后果返回集映射等,而且每个环节波及的内容都很多,每个接口都能够抽出独自剖析,后续有工夫再一一具体的看看。前面还是再剖析一下插件的利用。

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

正文完
 0