前言

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

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

SQL执行过程简介

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

听我的,看完MyBatis执行过程后你再去面试_第1张图片

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

SqlSession

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

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

听我的,看完MyBatis执行过程后你再去面试_第2张图片

在生成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

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

  • MapperProxy代理类生成流程

  • MapperProxy代理类执行操作

MapperProxy代理类生成流程

听我的,看完MyBatis执行过程后你再去面试_第3张图片

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

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

public  T getMapper(Class type, SqlSession sqlSession) {
       MapperProxyFactory 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怎么是实现的呢?MyBatis 的 Mapper接口 是通过动态代理实现的,调用 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);
  }

听我的,看完MyBatis执行过程后你再去面试_第4张图片

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

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 动态代理类图结构如下所示:

  • 听我的,看完MyBatis执行过程后你再去面试_第5张图片

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

public  List 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 是最顶层的是执行器,它有两个实现类,分别是BaseExecutor和 CachingExecutor

  • BaseExecutor 是一个抽象类,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。BaseExecutor基于适配器设计模式之接口适配会有三个子类,分别是 SimpleExecutorReuseExecutor 和 BatchExecutor

    • BatchExecutor : 批处理执行器,用于执行update(没有select,JDBC批处理不支持select将多个 SQL 一次性输出到数据库,

    • ReuseExecutor : 可重用执行器, 执行updateselect,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象

    • SimpleExecutor : 是 MyBatis 中默认简单执行器,每执行一次updateselect,就开启一个Statement对象,用完立刻关闭Statement对象

  • CachingExecutor: 缓存执行器,为Executor对象增加了二级缓存的相关功:先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器。

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

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

听我的,看完MyBatis执行过程后你再去面试_第6张图片

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

public  List 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  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
       //获取查询语句对应的二级缓存
  Cache cache = ms.getCache();
       //sql查询是否存在在二级缓存中
     if (cache != null) {
           //根据