源码解析:mybatis调用链之执行sql语句并返回结果(一级缓存和二级缓存解析)

此流程以一个调用例子为说明,如下

TUser user = mapper.selectByPrimaryKey(1);

在mapper(代理对象)调用方法时,进入MapperProxyinvoke方法,

MapperProxy

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {//如果是Object本身的方法不增强
        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);
    //调用execute方法执行sql
    return mapperMethod.execute(sqlSession, args);
  }

可以看到,在invoke方法中,会判断Object,不进行拦截增强,而是直接执行后返回。

如果是mapper本身的方法,则需要从缓存中获取MapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中

MapperProxy

private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }

MapperMethod有两个属性SqlCommandMethodSignature,其作用分别为:

1、SqlCommand从configuration中获取方法的命名空间.方法名以及SQL语句的类型

2、MethodSignature封装mapper接口方法的相关信息(入参,返回类型);

MapperMethod

//从configuration中获取方法的命名空间.方法名以及SQL语句的类型
private final SqlCommand command;
//封装mapper接口方法的相关信息(入参,返回类型);
private final MethodSignature method;

......

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

在初始化对象实例之时,会初始化这两个属性SqlCommandMethodSignature

初始化SqlCommand对象,

MapperMethod.SqlCommand

public SqlCommand(Configuration configuration, Class mapperInterface, Method method) {
      final String methodName = method.getName();//获取方法名称
      final Class declaringClass = method.getDeclaringClass();
      //从configuration中获取mappedStatement
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if(method.getAnnotation(Flush.class) != null){
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {//如果mappedStatement不为空
        name = ms.getId();//获取sql的名称,命名空间+方法名称
        type = ms.getSqlCommandType();//获取sql语句的类型
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

初始化SqlCommand,从上面代码可以出,除了方法名、Class对象外,还需要调用MapperMethodresolveMappedStatement方法从configuration中获取MappedStatement实例对象,用于为SqlCommand属性name和type赋值。

MapperMethod.SqlCommand

//从configuration中获取mappedStatement
private MappedStatement resolveMappedStatement(Class mapperInterface, String methodName,
    Class declaringClass, Configuration configuration) {
  //sql语句的id为命名空间+方法名字
  String statementId = mapperInterface.getName() + "." + methodName;
  if (configuration.hasStatement(statementId)) {
    return configuration.getMappedStatement(statementId);//从configuration中获取mappedStatement
  } else if (mapperInterface.equals(declaringClass)) {
    return null;
  }
  for (Class superInterface : mapperInterface.getInterfaces()) {
    if (declaringClass.isAssignableFrom(superInterface)) {
      MappedStatement ms = resolveMappedStatement(superInterface, methodName,
          declaringClass, configuration);
      if (ms != null) {
        return ms;
      }
    }
  }
  return null;
}
}

从上面代码可以看到,sql语句的id为命名空间+方法名字,因此通过命名空间+方法名字从configuration中获取MappedStatement。为SqlCommand的属性sql的名称name、sql语句的类型type赋值,

注:MappedStatement的具体解析以及生成过程请参考mybatis调用链之XMLStatementBuilder解析解析sql语句节点

初始化MethodSignature对象,

MapperMethod.MethodSignature

public MethodSignature(Configuration configuration, Class mapperInterface, Method method) {
      //通过类型解析器获取方法的返回值类型
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class) {
        this.returnType = (Class) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      //初始化返回值等字段
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.returnsOptional = Jdk.optionalExists && Optional.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

初始化MethodSignature,封装mapper接口方法的相关信息(入参,返回类型);

至此,MapperMethod初始化完成,并存储到methodCache中缓存起来。methodCache的key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享。

然后调用MapperMethod的execute方法执行sql,

MapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {
    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()) {//返回值为void
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {//返回值为集合或者数组
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {//返回值为map
          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 = OptionalUtil.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;
  }

上述方法,根据sql语句类型以及接口返回的参数选择调用SqlSession不同的执行语句。

sql语句类型分为:

INSERT

UPDATE

DELETE

SELECT

        分为返回值为void

        返回值为集合或者数组

        返回值为map

        返回值为游标

        返回为单一对象的情况

                调用SqlSession的selectOne方法,SqlSession接口实现实例为DefaultSqlSession

DefaultSqlSession

@Override
  public  T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List 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;
    }
  }
  
......
......

  @Override
  public  List selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
  
  @Override
  public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //从configuration中获取要执行的sql语句的配置信息
      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();
    }
  }

从configuration中获取要执行的sql语句的配置信息(MappedStatement),调用Executorquery方法,执行sql语句,并返回指定的结果集。

Executor的实现类有BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor

其中BatchExecutor、ReuseExecutor、SimpleExecutor都是继承于抽象类BaseExecutor。BaseExecutor实现了executor接口的大部分方法,主要提供了缓存管理(一级缓存)和事务管理的能力,其他子类需要实现的抽象方法为:doUpdate,doQuery等方法;

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

这里我们以Executor的实现类CachingExecutor为例,继续如下过程:

CachingExecutor

@Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //获取sql语句信息,包括占位符,参数等信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
  //拼装缓存的key值
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

该CachingExecutor.query方法关键步骤如下:

CachingExecutor.query——>> 1、获取sql语句信息,包括占位符,参数等信息

CachingExecutor.query——>> 2、拼装缓存的key值

CachingExecutor.query——>> 3、调用CachingExecutor的query重载方法。

CachingExecutor

@Override
  public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //从MappedStatement中获取二级缓存
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List list = (List) tcm.getObject(cache, key);//从二级缓存中获取数据
        if (list == null) {
          //二级缓存为空,才会调用BaseExecutor.query
          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);
  }

        可以看到,该方法封装了二级缓存的能力。

        首先从MappedStatement中获取二级缓存,如果二级缓存未存在(表示未开启二级缓存,说明若是没有配置cache,则会关闭二级缓存),则直接从数据库中查询。

        注意:若是Configuration的属性cacheEnabled设置为false,则执行器Executor不会增加二级缓存能力,这个时候,二级缓存也不开启。

        上面方法表明,二级缓存由cache和key两个值唯一确定。这表明,二级缓存以每一个MappedStatement(也就是每一个具体的增删改查节点)为分界,再以入参进行区分。表示同一个mapper方法,入参不同,会有不同的缓存。另外即使不同的mapper方法查询数据是一致的,其缓存也是不同的。

        若是二级缓存存在,则尝试根据key从二级缓存中获取数据,二级缓存为空,才会调用BaseExecutor.query从数据库中查询

BaseExecutor

protected PerpetualCache localCache;//一级缓存的实现,PerpetualCache

......

@SuppressWarnings("unchecked")
  @Override
  public  List 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) {//检查当前executor是否关闭
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {//非嵌套查询,并且FlushCache配置为true,则需要清空一级缓存
      clearLocalCache();
    }
    List list;
    try {
      queryStack++;//查询层次加一
      list = resultHandler == null ? (List) 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) {//如果当前sql的一级缓存配置为STATEMENT,查询完既清空一集缓存
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

可以看到,在BaseExecutorquery方法种,先尝试从一级缓存种获取数据,缓存未命中,则从数据库中加载数据。

不难看出,整体的mybatis查询过程,首先是去查询二级缓存,二级缓存若是没有数据,则会去查询一级缓存,一级缓存没有数据,这时,才会从数据库获取数据。

一级缓存的实现由PerpetualCache完成,缓存数据保存在其内的Map cache = new HashMap<>();属性中。这说明一级缓存在本质上是一个大的map,且由于PerpetualCache是Executor(BaseExecutor)的属性,而每一个Executor又属于一个SqlSession,所以说一级缓存属于SqlSession级别的,由key(是根据sql语句、入参等算出来的唯一值)值区分。

另外,我们可以看到,上述query方法是BaseExecutor抽象类中的方法,Executor的实现类,除了CachingExecutor外,都继承了BaseExecutor,且这些是西安类并未重写public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException方法,因此,都会调用BaseExecutor的该query方法。

这表明mybatis并未提供一级缓存的关闭能力。

不过,在上述代码中表示,若是MappedStatement.isFlushCacheRequired配置为true,则需要清空一级缓存。这说明若在在sql语句的节点出处,配置flushCache为true,效果上相当于关闭了该sql的一级缓存。

除此之外,若在mybatis-config中配置setting值localCacheScope为STATEMENT,则查询完既清空一集缓存。这表明全局清空一级缓存,效果上相当于关闭了一级缓存。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

上述如果未从缓存中(包括一级和二级)获取到数据,则调用BaseExecutorqueryFromDatabase方法查询数据库,如下所示

BaseExecutor

//真正访问数据库获取结果的方法
  private  List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);//在缓存中添加占位符
    try {
      //调用抽象方法doQuery,方法查询数据库并返回结果,可选的实现包括:simple、reuse、batch
      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;
  }

BaseExecutor.queryFromDatabase方法有如下关键步骤:

BaseExecutor.queryFromDatabase——>> 1、在缓存中添加占位符

BaseExecutor.queryFromDatabase——>> 2、调用BaseExecutor的抽象方法doQuery,方法查询数据库并返回结果,可选的实现包括:SimpleExecutor、ReuseExecutor、BatchExecutor,在这里我们使用的是SimpleExecutor,代码如下所示:

//查询的实现
  public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();//获取configuration对象
      //创建StatementHandler对象,
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      //通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

这里主要分为三步:

1、创建StatementHandler对象

Configuration

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

        创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现

RoutingStatementHandler

public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //RoutingStatementHandler最主要的功能就是根据mappedStatment的配置,生成一个对应的StatementHandler对象并赋值给delegate
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

        我们这里以PreparedStatementHandler为例,展开讨论,新建PreparedStatementHandler对象,

PreparedStatementHandler

public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

        进入PreparedStatementHandler父类BaseStatementHandler构造函数,

BaseStatementHandler

protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

        可以看到,这里在ParameterHandler对象创建完后,使用了interceptorChain对ParameterHandler进行拦截器包装,与之相同的还有ExecutorResultSetHandlerStatementHandler共称为mybatis拦截器拦截四大对象

        至此,StatementHandler对象创建完成,与此同时,还创建了ResultSetHandlerParameterHandler对象。ResultSetHandlerParameterHandler对象在StatementHandler对象内。

2、调用prepareStatement方法,StatementHandler对象创建stmt,并使用parameterHandler对占位符进行处理

SimpleExecutor

//创建Statement
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取connection对象的动态代理,添加日志能力;
    Connection connection = getConnection(statementLog);
    //通过不同的StatementHandler,利用connection创建(prepare)Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    //使用parameterHandler处理占位符
    handler.parameterize(stmt);
    return stmt;
  }

        该方法做了以下几件事:

        获取connection对象(数据库连接)的动态代理,添加日志能力;

        通过不同的StatementHandler,利用connection创建(prepare)Statement;

RoutingStatementHandler

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

BaseStatementHandler

//使用模板模式,定义了获取Statement的步骤,其子类实现实例化Statement的具体的方式;
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //通过不同的子类实例化不同的Statement,分为三类:simple(statment)、prepare(prepareStatement)、callable(CallableStatementHandler)
      statement = instantiateStatement(connection);
      //设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      //设置数据集大小
      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,分为三类:simple(statment)、prepare(prepareStatement)、callable(CallableStatementHandler);

        我们这里以PreparedStatement为例,

PreparedStatementHandler

//使用底层的prepareStatement对象来完成对数据库的操作
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    //根据mappedStatement.getKeyGenerator字段,创建prepareStatement
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {//对于insert语句
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        //返回数据库生成的主键
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        //返回数据库生成的主键填充至keyColumnNames中指定的列
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
     //设置结果集是否可以滚动以及其游标是否可以上下移动,设置结果集是否可更新
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      //创建普通的prepareStatement对象
      return connection.prepareStatement(sql);
    }
  }

通过Connection对象的prepareStatement方法创建Statement

在这里,实际上执行的是Connection的代理对象,由于我们使用了ConnectionLogger代理类,实现了InvocationHandler接口)对Connection对象进行了拦截,进入ConnectionLogger执行拦截方法。

//对连接的增强
  public Object invoke(Object proxy, Method method, Object[] params)
      throws Throwable {
    try {
        //如果是从Obeject继承的方法直接忽略
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }
      //如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句
      //并返回prepareStatement的代理对象,让prepareStatement也具备日志能力,打印参数
      if ("prepareStatement".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql语句
        }        
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);//创建代理对象
        return stmt;
      } else if ("prepareCall".equals(method.getName())) {
        if (isDebugEnabled()) {
          debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql语句
        }        
        PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);//创建代理对象
        stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
        return stmt;
      } else if ("createStatement".equals(method.getName())) {
        Statement stmt = (Statement) method.invoke(connection, params);
        stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);//创建代理对象
        return stmt;
      } else {
        return method.invoke(connection, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

在这里,由于我们拦截的方法为PreparedStatement,因此,执行prepareStatement方法,返回PreparedStatement,并通过PreparedStatementLogger创建PreparedStatement的代理对象,为PreparedStatement增加日志功能。

PreparedStatementLogger

public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
    InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
    ClassLoader cl = PreparedStatement.class.getClassLoader();
    return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
  }

        使用parameterHandler处理占位符;

        调用StatementHandler的parameterize方法,

RoutingStatementHandler

@Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

PreparedStatementHandler

//使用parameterHandler对sql语句的占位符进行处理
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

可以看到,这里使用ParameterHandler对sql语句的占位符进行处理(这里的parameterHandler在前面创建过,为四大对象之一,为StatementHandler的属性变量。

DefaultParameterHandler

public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    //从boundSql中获取sql语句的占位符对应的参数信息
    List parameterMappings = boundSql.getParameterMappings();
    //遍历这个参数列表,把参数设置到PreparedStatement中
    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)) { // 获取对应的实参值
            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);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();//从parameterMapping中获取typeHandler对象
          JdbcType jdbcType = parameterMapping.getJdbcType();//获取参数对应的jdbcType
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
             //为statment中的占位符绑定参数
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

3、通过statementHandler对象调用ResultSetHandler将结果集转化为指定对象返回

        调用StatementHandler的query方法,从数据中心查询,

RoutingStatementHandler

@Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

PreparedStatementHandler

 @Override
  public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler. handleResultSets(ps);
  }

执行PreparedStatement的execute方法,实际上,是执行PreparedStatement的代理对象,由于我们使用PreparedStatementLogger对该对象进行代理,进入PreparedStatementLogger代理类,执行invoke方法,

PreparedStatementLogger

public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, params);
      }          
      if (EXECUTE_METHODS.contains(method.getName())) {
        if (isDebugEnabled()) {
          debug("Parameters: " + getParameterValueString(), true);
        }
        clearColumnInfo();
        if ("executeQuery".equals(method.getName())) {
          ResultSet rs = (ResultSet) method.invoke(statement, params);
          return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
        } else {
          return method.invoke(statement, params);
        }
      } else if (SET_METHODS.contains(method.getName())) {
        if ("setNull".equals(method.getName())) {
          setColumn(params[0], null);
        } else {
          setColumn(params[0], params[1]);
        }
        return method.invoke(statement, params);
      } else if ("getResultSet".equals(method.getName())) {
        ResultSet rs = (ResultSet) method.invoke(statement, params);
        return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
      } else if ("getUpdateCount".equals(method.getName())) {
        int updateCount = (Integer) method.invoke(statement, params);
        if (updateCount != -1) {
          debug("   Updates: " + updateCount, false);
        }
        return updateCount;
      } else {
        return method.invoke(statement, params);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

        查询完结果后,通过ResultSetHandler.handleResultSets处理结果集。结果集存储在Statement中。

DefaultResultSetHandler

public List handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
    //用于保存结果集对象
    final List multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //statment可能返回多个结果集对象,这里先取出第一个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //获取结果集对应resultMap,本质就是获取字段与java属性的映射规则
    List resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);//结果集和resultMap不能为空,为空抛出异常
    while (rsw != null && resultMapCount > resultSetCount) {
     //获取当前结果集对应的resultMap
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);//获取下一个结果集
      cleanUpAfterHandlingResultSet();//清空nestedResultObjects对象
      resultSetCount++;
    }
    //获取多结果集。多结果集一般出现在存储过程的执行,存储过程返回多个resultset,
    //mappedStatement.resultSets属性列出多个结果集的名称,用逗号分割;
    //多结果集的处理不是重点,暂时不分析
    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++;
      }
    }

    return collapseSingleResultList(multipleResults);
  } 
  

        然后,获取结果集对应resultMap,本质就是获取字段与java属性的映射规则

        ResultSetHandler.handleResultSets——>> 2、获取结果集对应resultMap,本质就是获取字段与java属性的映射规则

        ResultSetHandler.handleResultSets——>> 3、获取当前结果集对应的resultMap

        ResultSetHandler.handleResultSets——>> 4、根据映射规则(resultMap)对结果集进行转化,转换成目标对象以后放入multipleResults中

DefaultResultSetHandler

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if (parentMapping != null) {//处理多结果集的嵌套映射
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {//如果resultHandler为空,实例化一个人默认的resultHandler
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          //对ResultSet进行映射,映射结果暂存在resultHandler中
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          //将暂存在resultHandler中的映射结果,填充到multipleResults
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
          //使用指定的rusultHandler进行转换
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
      // issue #228 (close resultsets)
      //调用resultset.close()关闭结果集
      closeResultSet(rsw.getResultSet());
    }
  } 
  

        DefaultResultSetHandler.handleResultSet分为以下关键步骤:

        DefaultResultSetHandler.handleResultSet——>> 1、处理多结果集的嵌套映射

        DefaultResultSetHandler.handleResultSet——>> 2、如果resultHandler为空,实例化一个默认的resultHandler

        DefaultResultSetHandler.handleResultSet——>> 3、该方法,对ResultSet进行映射,映射结果暂存在resultHandler中

DefaultResultSetHandler

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    if (resultMap.hasNestedResultMaps()) {//处理有嵌套resultmap的情况
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {//处理没有嵌套resultmap的情况
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

        DefaultResultSetHandler.handleRowValues分为以下几个关键步骤:

        DefaultResultSetHandler.handleRowValues——>> 1、处理有嵌套resultmap的情况

DefaultResultSetHandler

private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    final DefaultResultContext resultContext = new DefaultResultContext<>();
    skipRows(rsw.getResultSet(), rowBounds);
    Object rowValue = previousRowValue;
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      Object partialObject = nestedResultObjects.get(rowKey);
      // issue #577 && #542
      if (mappedStatement.isResultOrdered()) {
        if (partialObject == null && rowValue != null) {
          nestedResultObjects.clear();
          storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      } else {
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        if (partialObject == null) {
          storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
        }
      }
    }
    if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
      previousRowValue = null;
    } else if (rowValue != null) {
      previousRowValue = rowValue;
    }
  } 
  

        DefaultResultSetHandler.handleRowValues——>> 2、处理没有嵌套resultmap的情况

DefaultResultSetHandler

//简单映射处理
  private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    //创建结果上下文,所谓的上下文就是专门在循环中缓存结果对象的
    DefaultResultContext resultContext = new DefaultResultContext<>();
    //1.根据分页信息,定位到指定的记录
    skipRows(rsw.getResultSet(), rowBounds);
    //2.shouldProcessMoreRows判断是否需要映射后续的结果,实际还是翻页处理,避免超过limit
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      //3.进一步完善resultMap信息,主要是处理鉴别器的信息
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      //4.读取resultSet中的一行记录并进行映射,转化并返回目标对象
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      //5.保存映射结果对象
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  } 
  

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap方法主要分为以下几种:

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap——>> 1、根据分页信息,定位到指定的记录

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap——>> 2、shouldProcessMoreRows判断是否需要映射后续的结果,实际还是翻页处理,避免超过limit

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap——>> 3、进一步完善resultMap信息,主要是处理鉴别器的信息

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap——>> 4、读取resultSet中的一行记录并进行映射,转化并返回目标对象

DefaultResultSetHandler

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //4.1 根据resultMap的type属性,实例化目标对象
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      //4.2 对目标对象进行封装得到metaObjcect,为后续的赋值操作做好准备
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;//取得是否使用构造函数初始化属性值
      if (shouldApplyAutomaticMappings(resultMap, false)) {//是否使用自动映射
         //4.3一般情况下 autoMappingBehavior默认值为PARTIAL,对未明确指定映射规则的字段进行自动映射
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
      }
       //4.4 映射resultMap中明确指定需要映射的列
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      //4.5 如果没有一个映射成功的属性,则根据的配置返回null或者结果对象
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

        DefaultResultSetHandler.getRowValue方法中分为以下几个关键步骤:

        DefaultResultSetHandler.getRowValue ——>> 1、根据resultMap的type属性,实例化目标对象

        DefaultResultSetHandler.getRowValue ——>> 2、对目标对象进行封装得到metaObjcect,为后续的赋值操作做好准备

        DefaultResultSetHandler.getRowValue ——>> 3、一般情况下 autoMappingBehavior默认值为PARTIAL,对未明确指定映射规则的字段进行自动映射

DefaultResultSetHandler

 //对未明确指定映射规则的字段进行自动映射
  private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
    //获取resultSet中存在的,但是ResultMap中没有明确映射的列,填充至autoMapping中
    List autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
    boolean foundValues = false;
    if (!autoMapping.isEmpty()) {
      //遍历autoMapping,通过自动匹配的方式为属性复制
      for (UnMappedColumnAutoMapping mapping : autoMapping) {
        //通过typeHandler从resultset中拿值
        final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          //通过metaObject给属性赋值
          metaObject.setValue(mapping.property, value);
        }
      }
    }
    return foundValues;
  }

        DefaultResultSetHandler.getRowValue ——>> 4、映射resultMap中明确指定需要映射的列

DefaultResultSetHandler

 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    //从resultMap中获取明确需要转换的列名集合
    final List mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    //获取ResultMapping集合
    final List propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);//获得列名,注意前缀的处理
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        //如果属性通过另外一个resultMap映射,则忽略
        column = null;
      }
      if (propertyMapping.isCompositeResult()//如果是嵌套查询,column={prop1=col1,prop2=col2}
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))//基本类型映射
          || propertyMapping.getResultSet() != null) {//嵌套查询的结果
        //获得属性值
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        //获得属性名称
        final String property = propertyMapping.getProperty();
        if (property == null) {//属性名为空跳出循环
          continue;
        } else if (value == DEFERED) {//属性名为DEFERED,延迟加载的处理
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          //通过metaObject为目标对象设置属性值
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

        DefaultResultSetHandler.getRowValue ——>> 5、如果没有一个映射成功的属性,则根据的配置返回null或者结果对象

        DefaultResultSetHandler.getRowValue ——>> end

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap——>> 5、保存映射结果对象

DefaultResultSetHandler

//保存映射结果对象
  private void storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
    if (parentMapping != null) {//如果是嵌套结果或嵌套查询,将对象保存至父对象
      linkToParents(rs, parentMapping, rowValue);
    } else {//普通映射则把对象保存至resultHandler和resultContext
      callResultHandler(resultHandler, resultContext, rowValue);
    }
  } 
  

        DefaultResultSetHandler.storeObject关键步骤:

        DefaultResultSetHandler.storeObject ——>> 1、如果是嵌套结果或嵌套查询,将对象保存至父对象

        DefaultResultSetHandler.storeObject ——>> 2、普通映射则把对象保存至resultHandler和resultContext

DefaultResultSetHandler

@SuppressWarnings("unchecked" /* because ResultHandler is always ResultHandler*/)
  private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) {
    resultContext.nextResultObject(rowValue);
    ((ResultHandler) resultHandler).handleResult(resultContext);
  } 
  

        在callResultHandler方法中,可以看到resultContex的作用是暂存映射结果对象,之后会放入DefaultResultHandler容器当中

        DefaultResultSetHandler.storeObject ——>> end

        DefaultResultSetHandler. handleRowValuesForSimpleResultMap——>> end

        DefaultResultSetHandler.handleResultSet——>> 4、将暂存在resultHandler中的映射结果,填充到multipleResults

        DefaultResultSetHandler.handleResultSet——>> 5、当配置指定的rusultHandler视,使用指定的rusultHandler进行转换

        DefaultResultSetHandler.handleResultSet——>> 6、调用resultset.close()关闭结果集

        DefaultResultSetHandler.handleResultSet——>> end

        ResultSetHandler.handleResultSets——>> 5、获取下一个结果集

DefaultResultSetHandler

private ResultSetWrapper getNextResultSet(Statement stmt) {
    // Making this method tolerant of bad JDBC drivers
    try {
      if (stmt.getConnection().getMetaData().supportsMultipleResultSets()) {
        // Crazy Standard JDBC way of determining if there are more results
        if (!(!stmt.getMoreResults() && stmt.getUpdateCount() == -1)) {
          ResultSet rs = stmt.getResultSet();
          if (rs == null) {
            return getNextResultSet(stmt);
          } else {
            return new ResultSetWrapper(rs, configuration);
          }
        }
      }
    } catch (Exception e) {
      // Intentionally ignored.
    }
    return null;
  }

        ResultSetHandler.handleResultSets——>> 5、获取下一个结果集

        ResultSetHandler.handleResultSets——>> 6、获取多结果集。多结果集一般出现在存储过程的执行,存储过程返回多个resultset,mappedStatement.resultSets属性列出多个结果集的名称,用逗号分割;多结果集的处理不是重点,暂时不分析。

        ResultSetHandler.handleResultSets——>> end

        至此,返回调用结果,查询调用链结束。

        BaseExecutor.queryFromDatabase——>> 3、在缓存中删除占位符

        BaseExecutor.queryFromDatabase——>> 4、将真正的结果对象添加到一级缓存

PerpetualCache

......
private Map cache = new HashMap<>();
......

@Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

        可以看到,一级缓存使用的是PerpetualCache,具体存储在属性cache(cache是一个map)中。

        BaseExecutor.queryFromDatabase——>> end

        CachingExecutor.query——>> end

你可能感兴趣的:(mybatis,sql,缓存)