Mybatis源码分析之(四)mapper访问数据库的底层原理(代理方法中具体访问数据库的细节)

x33g5p2x  于2022-01-13 转载在 其他  
字(9.3k)|赞(0)|评价(0)|浏览(320)

从之前的文章,我们知道了其实mapper真正执行的方法就下面的最后两行。(以下所有的分析都基于一次mybatis的一次select查询。

MapperProxy类中的invoke函数

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
   //获取MapperMethod 
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行查询并且返回结果
    return mapperMethod.execute(sqlSession, args);
  }

MapperMthod

private MapperMethod cachedMapperMethod(Method method) {
 //methodCache其实就是一个缓存,将方法与mapperMethod作为一组键值对进行缓存
    MapperMethod mapperMethod = methodCache.get(method);
    //若缓存中没找到则生成一个,在把生成的mapperMethod加入缓存
    if (mapperMethod == null) {
    //生成mapperMethod的方法
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

这里解释一下mapperMethod有什么作用,首先看一下MapperMethod类的具体参数

public class MapperMethod {

//记录方法是什么类型的方法(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;)
  private final SqlCommand command;
  //记录方法的具体情况(比如说返回一个还是多个,方法返回类型,方法的参数是啥等信息)
  private final MethodSignature method;

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

}

//执行方法
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //通过command的类型去找实际需要执行的方法,不一个个分析了,只以select的executeForMany为例子
    //其他都是差不了多少的
    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);
        }
        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;
  }

//查询方法
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //
    Object param = method.convertArgsToSqlCommandParam(args);
    //是否需要分页
    if (method.hasRowBounds()) {
    //拿到rowBounds
      RowBounds rowBounds = method.extractRowBounds(args);
      //查询
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
    //查询,进入这条
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

//实际还是走的有RowBounds 的语句,只是给了默认值,默认值是拿到Integer的最大值
  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    //从configuration获取MappedStatement (在初始化的时候就已经放到缓存中了,这里只是获取一下)
      MappedStatement ms = configuration.getMappedStatement(statement);
      //通过executor来查询
      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();
    }
  }

因为mybatis默认开始Cache,所以我们的Executor是CachingExecutor。但是我们没在mapper.xml中配置Cache的属性,所以最终是没缓存功能的。通过装饰器模式来增加了Executor的功能

Executor

//这两个方法是CachingExecutor里面的
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  //生成BoundSql,里面存放着sql语句
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //生成缓存用的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      //因为mapper中没有配置cache,所以这里的cahce是没有的
    Cache cache = ms.getCache();
    //cache为空,所以不会走这里面的逻辑
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //到来这里,通过delegate来执行query(因为CachingExecutor是一个装饰类,delegate是原始类(在这里是SimpleExecutor))
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
@SuppressWarnings("unchecked")
  @Override
  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是一级缓存,是在BaseExecutor中的
      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;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //先从key中放个占位的值
    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;
  }
@Override
 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,对Statement进行处理
     StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
     //生成statement
     stmt = prepareStatement(handler, ms.getStatementLog());
     //进行访问
     return handler.<E>query(stmt, resultHandler);
   } finally {
     closeStatement(stmt);
   }
 }

//这里是prepareStatement函数,用来生成访问数据库需要的Statement对象
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
   Statement stmt;
   //如果是debug状态则对sqlSession进行代理,因为要打印log
   Connection connection = getConnection(statementLog);
   //
   stmt = handler.prepare(connection, transaction.getTimeout());
   //对参数进行处理
   handler.parameterize(stmt);
   return stmt;
 }

我们进handler的prepare方法看看

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
    //初始化statement 
      statement = instantiateStatement(connection);
      //设置超时
      setStatementTimeout(statement, transactionTimeout);
      //设置fetchSize(mysql不起作用,不支持这个)
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement. Cause: " + e, e);
    }
  }

之后得到了Statement对象后,要去访问数据库了。(上面的return handler.query(stmt, resultHandler);)调用的是下面的query方法

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.<E>query(statement, resultHandler);
  }1234
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  //将Statement强转成PreparedStatement
    PreparedStatement ps = (PreparedStatement) statement;
    //对数据库进行查询(对数据库进行查询是jdbc做的事情,mybatis也只是对jdbc进行了包装)
    ps.execute();
    //对返回结果进行处理
    return resultSetHandler.<E> handleResultSets(ps);
  }

到这里,我们就进行了一次对数据库的访问,并拿到了数据。数据此时在Statement的ResultSet里面,
如果你熟悉jdbc的话,一定不陌生下面的代码,我们拿到resultSet后的处理方式如下。

ResultSet resultSet = preparedStatement.executeQuery();
        while(resultSet.next()) {
            String string = resultSet.getString(1);

        }

相关文章