Mybatis的运行流程源码分析

目录

前言

一、整体流程

二、查询操作

1.MapperProxy

2.DefaultSqlSession

3.Executor

4.statementHandler

三、查询操作

总结


前言

        本文以查询的情况为例,主要分析mybatis的处理过程。mybatis处理增删改查主要分为了select和update两种方式,insert和delete操作也使用update操作进行处理。


一、整体流程

        在mapper代理对象构建完成之后,调用查询方法,依次经过DefaultSqlSession,Executor和StatementHandler的相关操作,获得返回数据,下面将进行具体分析。

二、查询操作

1.MapperProxy

        MapperProxy中为每个method创建一个MapperMethodInvoker代理对象,并将其放入缓存中,以后可以直接获取。

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        //执行PlainMethodInvoker的invoke方法,本来已经是一个代理对象,然后调用另一个代理对象的invoke方法,为啥啊
        //Map<Method, MapperMethodInvoker> methodCache有缓存的作用,存储的新建的代理对象
        //前面是对mapper对象创建代理对象,后面是为方法创建代理对象,并加入缓存,invoke方法中调用MapperMethod的execute方法
        //创建方法代理对象费时的是MapperMethod对象创建,缓存节省时间,不一开始就全部创建是因为可能不会全部用到,多次调用同一方法不用重复创建
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

         MapperProxy中持有methodCache,里面存储着当前这个代理对象中的方法生成的cacheInvoker对象,cacheInvoker对象中保存了方法的返回值,参数,注解等信息以及对应的sql信息,在下次调用时不必再次重新解析生成,可以直接使用。这里我有些好奇methodCache是如何保证并发问题,后来看到是一个ConcurrentHashmap,有点意思的。

 private final Map<Method, MapperMethodInvoker> methodCache;
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
        return invoker;
      }
      //为method创建MapperMethodInvoker代理对象并加入缓存中
      return methodCache.computeIfAbsent(method, m -> {
          //生成MapperMethod对象,记录sqlcommand和MethodSignature
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      });
    }
  }

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

 invoke方法会直接执行mapperMethod的execute方法,在该方法中,对参数进行解析,并构建parameterObject,会根据方法的类别分别调用sqlSession的不同方法进行处理。

    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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());
    }
    return result;
  }

        值得注意的是,对于select方法,其实sql返回的都是list,mybatis是通过方法声明的返回值来确定通过何种方式查询,其实底层都是通过selectLIst方法查询,对于单个对象就是取其中的第一个。如果使用一个对象接收一个本来是list的结果,如果上面的逻辑判断,会取第一个,好像没有问题,但是mybatis在取值的时候做了判断,如果返回对象个数超过1就会报错,个数为0会返回null。

 public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

2.DefaultSqlSession

       本文以查询为例,会调用DefaultSqlSession的selectList方法,首先通过类名+方法名作为id查询对应的MapperStatement,然后调用executor的query方法进行处理。

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //在mapperStatement中选取,id是类名+方法名
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

3.Executor

       在sqlSession中会调用CachingExecutor的query方法,首先是获取boundsql,会完成对#符和$符的初步处理,对于$符,会直接以字符串拼接的方式替换为真实值;对于#符,会替换为?,作为预编译语句,后面还会进一步解析。然后使用mapperStatement,parameterObject,rowBounds和boundSql构建key。

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    /*
      boundSql 就是执行的sql,在此处会对$符和#进行解析,$返回真实参数,#返回占位符?,后面再进行解析
     */
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    /*
      创建缓存,缓存的key值由mapperStatement,参数,rowBounds,和boundsql共同决定
     */
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 将sql语句、缓存key都传入query方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

       在query方法中,在有二级缓存的情况下,首先从二级缓存中查找数据,在无法查到的情况下,会调用BaseExecutor的query方法,在一级缓存中查询,并将查询结果写入二级缓存中;在没有二级缓存的情况下,会直接在一级缓存中查询。

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    /*
      mybatis的二级缓存
     */
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //进一步查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

          在BeseExecutor的query方法中,首先会从一级缓存中查询数据,如果没有查询到,会调用queryFromDatabase方法,从数据库中进行查询,拿到结果之后写入缓存中。

  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++;
      /*
        去localCache本地缓存中查,一级缓存
       */
      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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

       在queryFromDatabase中,首先在localCache以当前key插入一个默认值,然后在数据库中进行查询,再将结果更新到缓存中。

  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);
    }
    // 将查询的结果缓存在本地
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

       在doQuery方法中,会调用prepareStatement方法对sql中的占位符进行处理,也就是对#符的进一步处理,而且在其中对一些特殊字符进行了处理,避免了sql注入的情况。然后调用StatementHandler的query方法得到最终结果。其实,对于参数的处理也是由statementHandler实现的,为啥要在executor中调用还要考虑。

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 创建语句处理器,同时创建了参数处理器和结果集处理器,可以进行拦截器的代理,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //语句预处理,进行#符的再次解析,将解析结果写入preparedStatement中,这里为了防止sql注入,对特殊字符进行了处理,对单引号会在单引号后面添加一个单引号
      //此处在executor中调用statementHandler的parameterHandler进行参数处理就有点意思,为啥不在后面statementHandler的处理中进行
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

4.statementHandler

       在statementHandler的query方法中,使用PreparedStatement的execute方法执行查询,并将结果进行处理之后返回。

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //预编译sql语句执行  JDBC的execute方法
    /*
      执行这个PreparedStatement对象中的SQL语句,该对象可以是任何类型的SQL语句。
      一些准备好的语句返回多个结果;execute方法可以处理这些复杂的语句,
      也可以处理由executeQuery和executeUpdate方法处理的更简单的语句。
      execute方法返回一个布尔值来表示第一个结果的形式。
      您必须调用方法getResultSet或getUpdateCount来检索结果;
      必须调用getMoreResults才能移动到任何后续结果。
     */
    ps.execute();
    //使用resultSetHandler处理结果集,参数resultHandler根本没有用啊
    return resultSetHandler.handleResultSets(ps);
  }

三、查询操作

               关于更新操作此处不做过多介绍,在executor的更新过程中,会将缓存清空。

  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    //清空缓存
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //清空缓存
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

       在PreparedStatementHandler中的update方法中,在更新完成之后,可以将主键写回到dto中。

  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    //获取主键生成器
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    //处理主键
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

总结

        本文对mybatis在运行过程中的一些操作进行了源码上的分析,主要是对sqlSession,executor和statementHandler的相关操作,重点分析了查询流程,并介绍了更新流程的不同之处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值