mybatis执行流程

前提:

本次讲解的环境是在spring与mybatis整合的情况下进行的源码分析,整合的部分就不细说了。

正文

我们从一个简单的查询开始

在这里插入图片描述
可以看到,我们自己定义的接口其实最终成为了 mapperProxy 类型的代理对象

在这里插入图片描述
这个类实现了 InvocationHandler接口,我们在调用我们编写的接口方法的时候实际上就是调用了这个类的invoke方法。这个类还封装了一些信息,使用的sqlsession,我们mapper接口的Class对象,还有缓存了我们接口的方法信息。

开始执行

执行的时候会进入到 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);
        //  然后判断是不是default修饰的 默认方法,如果是直接执行
      } else if (method.isDefault()) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // 最后看mapperCache 里有没有对应的mapperMethod ,有的话直接取出来,拿去执行,没有的话就创建一个,缓存取来,然后去执行
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

这里我要额外说一下上面提到的cachedMapperMethod方法

private MapperMethod cachedMapperMethod(Method method) {
   //  这里使用了map接口提供的computeIfAbsent方法
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
// 这个方法我觉得mybatis使用的很巧妙。。。
  default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
            // 先判断给定的Function函数是不是null,是的话直接抛出异常
        Objects.requireNonNull(mappingFunction);
        V v;
        // 之后判断这个map里根据给定的key能不能取到值

		// 如果取不到,是null,就调用给定的Function的apply方法生成一个值并且这个值也不是null的话把它存起来,然后返回去,如果这个function产生的值也是null的话那么就不会保存,最终返回去的那个V也会是null
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }
        // 取到了当然直接返回值就可以了
        return v;
    }

接下来就该执行mapperMethod.execute方法了。

mapperMethod.execute

 public Object execute(SqlSession sqlSession, Object[] args) {
 //  首先这个 result 来保存最终的处理结果
    Object result;
    //  然后判断执行的这个sql是什么类型的,增删改查等等类型,如果没有对应上的就报错了
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // 最后检测结果是否合法
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    // 返回执行结果
    return result;
  }

在这里插入图片描述

由于我这个是一个查询全部的操作,所以走到了executeForMany这里,将查询到的结果返回。

executeForMany

在这里插入图片描述
我们可以看到:

  • 首先将我们接口的方法的参数转换成了sql的参数
  • 由于我们没有设置rowbound,所以直接就执行了sqlsession的selectList方法。

再额外说一些:

  • 其实我们在程序中只是调用了我们的接口的抽象方法,但是这里本质上是调用了sqlsession的方法来执行的。我们程序中使用面向接口编程的方式也很简洁,清晰,是一种很好的编程方式。
  • sqlsession本质上是一个接口,这里是使用的 SqlSessionTemplate来实现的。SqlSessionTemplate是spring提供的,也就是mybatis和spring整合在一起了。spring在和其他的框架整合的时候也会有其他的 xxxTemplate,比如和kafka整合的时候有kafkaTemplate,与es整合的时候有elasticsearchTemplate等等。我们先看一下spring提供的SqlSessionTemplate吧!

在这里插入图片描述

可以看到,SqlSessionTemplate封装了sqlSessionFactory,ExecutorType 和 一个内部的sqlSessionProxy。注释说明SqlSessionTemplate是线程安全的,由spring管理的,可以和spring的事务关联在一起的,内部管理者与数据库打交道的session的生命周期,提交或者回滚等信息,也就是sqlSessionProxy

 /**
   * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
   * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
   * {@code PersistenceExceptionTranslator}.
   */
  private class SqlSessionInterceptor implements InvocationHandler {
  // 最终 SqlSessionTemplate 操作的方法会执行到这里
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 首先根据sqlSessionFactory,executorType等信息创建出一个真正的SqlSession,也就是mybatis提供的DefaultSqlSession,然后管理者这个DefaultSqlSession的生命周期
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
      // 这里开始使用DefaultSqlSession来执行了
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          //  这里也说了,有的数据库需要手动使用sqlSession来提交事务,我使用的mysql不用的
          sqlSession.commit(true);
        }
        // 这里只需要返回结果就可以了
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          //  出异常了就关闭连接,sqlsession
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        // 最后还要将这个异常跑出去
        throw unwrapped;
      } finally {
      // 确保最终关闭SqlSession,无论有没有异常
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

在我这个案例中事务是由spring进行管理的,所以这里你并没有看到DefaultSqlSession的关于事务的操作。原始是spring的事务管理器会根据这个方法是不是成功的执行了,如果没有抛出异常,就认为是成功了,提交事务,有异常就失败了,关闭事务。代码层面就是一个try…cache…过程。所以我们开发中service方法产生的异常如果自己try…cache了有可能导致提交了而不是回滚

DefaultSqlSession

在这里插入图片描述
通过层层的包装,最终还是来到了mybatis的DefaultSqlSession来执行。
在这里插入图片描述
来到了DefaultSqlSession的selectList方法。
在这里插入图片描述
这里对我们的方法参数进行了一些封装,最终形成了一个map,,反回了。你可以看到对集合类型和数组类型的包装,以便于我们在写mapper.xml文件的时候进行动态的获取其中的值。

执行之后就获取到了查询结果

executor 的执行

在这里插入图片描述
我们一直跟进下去,会发现最终这个执行器是SimpleExecutor,也就是mybatis的默认执行器,来调用他的query方法。

// 首先这个方法传入了必要的信息,我们的mapper,参数等等
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 (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 先查询本地缓存有没有数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
      // 这里就是真正的从数据库中查询数据了
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // 这里还判断了缓存是 session 级别的还是 statment 级别的,也就是mybatis的一级缓存和二级缓存
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

我们再看一下 queryFromDatabase

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    // 这里获取到了从数据库的查询结果
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    // 将结果放入了缓存中  也就是一级缓存中,本质是一个 hashMap
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

在这里插入图片描述
这里可以看到是使用了PreparedStatement来执行,也就是JDBC api里的对象,mybatis对此进行了封装,之后将查询结果也进行了处理,最终返回给了我们。

到此一次查询就结束了!!!

总结

我们在调用我们接口的方法时,本质上是调用了代理对象的方法(mapperProxy),进而调用了sqlSessionTemplate来执行,而它的内部维护了一个sqlsession。每次我们对数据库操作的时候都会生成一个sqlsession,而底层则是对JDBC进行了封装,从而获得处理结果。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值