文章目录

  • 一、开篇
  • 二、源码分析
  • 1.selectList()
  • 2.CachingExecutor
  • 3、createCacheKey
  • 1)为什么CachingExecutor要调用SimpleExecutor的方法去创建缓存key呢?
  • 4、创建好缓存key之后:query查询二级缓存
  • 5、如果二级缓存为空继续走查询query查询一级缓存
  • 6、查询一级缓存为空queryFromDatabase查询数据库
  • 三、演示
  • 四、总结



一、开篇

java中mapper查询数据太多导出out of heap space mapper.selectlist_二级缓存


在我们分析Executor的时候最后一步就是查询我们的数据库 本篇就是去深究一下Mybatis怎样帮我们封装查询数据库的呢?

二、源码分析

1.selectList()

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
    // 拿到封装好的Mapper对应关系以及SQL语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      //执行query去查询数据
      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();
    }
  }

我们看到在SelectList中第一步帮我们拿到了封装的SQL语句然后去执行query方法

2.CachingExecutor

java中mapper查询数据太多导出out of heap space mapper.selectlist_mybatis_02


我们看源码的时候他又两个实现类,那么会走哪个以及为什么走CachingExecutor这个呢?我们在分析openSession的时候就分析郭了,Executor初始话的时候会先创建简单的然后最后创建并且返回的是CachingExecutor所以我们可以断定这步执行的是CachingExecutor;废话少说直接看代码

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);//拿到SQL语句
    
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);//创建缓存key
    
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

3、createCacheKey

@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
 
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
    
  }

这里我们可以去看到delegate.createCacheKey,是调用了简单执行器的方法去执行的,自己的方法调用别人的那么问题来了

1)为什么CachingExecutor要调用SimpleExecutor的方法去创建缓存key呢?

Mybatis缓存时先查二级缓存,再查以及缓存。当二级缓存中没有的话才去查一级缓存
二级缓存和一级缓存都需要创建缓存key这样写方便代码重构和减少冗余

所以我们可以理解为二级缓存实质上是一个空壳

4、创建好缓存key之后:query查询二级缓存

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) { //如果二级缓存不为空的话直接返回二级缓存中的list 
      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;
      }
    }
    //如果二级缓存为空
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

5、如果二级缓存为空继续走查询query查询一级缓存

@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();
    }
    //queryStack 为了保证线程安全 再执行clearLocalCache操作的时候没有线程对再去queryStack++
    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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

queryStack为了保证线程安全,但是这里有个问题queryStack没有用原子类所以再并发条件先会出现问题的

protected int queryStack = 0;

6、查询一级缓存为空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);//EXECUTION_PLACEHOLDER占位符
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);//查询结果
    } finally {
      localCache.removeObject(key);/清除掉之前的带占位符的key
    }
    localCache.putObject(key, list);//添加结果到一级缓存中去
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;/返回结果集
  }

三、演示

public class Test {
    public static void main(String[] args) {
        try {
            //1、定义Mybatis文件
            String resource = "mybatis_config.xml";
            //2、IO操作 使用java的api读取配置文件获取InputStreamReader
            Reader resourceAsReader = Resources.getResourceAsReader(resource);
            //3、获取SQLSessionFactoryBuilder
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
            //4、通过获取SQLSessionFactoryBuilder获取session
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //5、通过SqlSession去操作Mapper 
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //6、执行SQL语句
            System.out.println("------------------------第一次调用-------------------------------");
            List<User> users = mapper.selectAllUser();
            for (User user : users) {
                System.out.println(user.toString());
            }
            System.out.println("------------------------第二次调用-------------------------------");
            List<User> users1 = mapper.selectAllUser();
            for (User u : users1) {
                System.out.println(u.toString());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我们打印结果

==>  Preparing: select * from user 
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 小王, 123456
<==        Row: 2, 小李, 123456
<==      Total: 2
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}
------------------------第二次调用-------------------------------
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}

可以看到第一次执行SQL语句第二次直接从一级缓存中取出来了
此时中间我们去手动加一行代码( sqlSession.clearCache();)再两次执行中间手动清除一级缓存看看结果

==>  Preparing: select * from user 
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 小王, 123456
<==        Row: 2, 小李, 123456
<==      Total: 2
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}
------------------------第二次调用-------------------------------
==>  Preparing: select * from user 
==> Parameters: 
<==    Columns: id, username, password
<==        Row: 1, 小王, 123456
<==        Row: 2, 小李, 123456
<==      Total: 2
User{id=1, username='小王', password='123456'}
User{id=2, username='小李', password='123456'}

此时可以看到我们两次都打印SQL语句,这是因为我们在执行完第一条SQL语句把我们的一级缓存清楚了

四、总结

通过以上分析,我们可以看到一级缓存是Mybatis中自带的,二级缓存其实就是一个空壳(在不配置的情况下)。