MyBatis 入门 (MyBatis 缓存应用)

为方便理解,本章涉及示例代码已上传至 gitee
==>获取示例代码请点击这里。。。
拉取示例代码时,请拉取所有分支,master 分支只是做了示例的初始化

MyBatis 的缓存一共有两种,一级缓存、二级缓存;一级缓存为框架的默认缓存,不可修改配置,二级缓存可以修改和配置,要理解 MyBatis 的缓存过程,首先需要对 MyBatis 的执行器有一定的了解,下图为 MyBatis 的各执行器之间的关系:
在这里插入图片描述
当初始化 MyBatis 配置的时候,会创建一个 CachingExecutor 类型的对象,其持有一个 Executor 类型的属性,所有的增删改查操作,先通过 CachingExecutor 进行一次处理,再交给 delegate 来执行;其中,CachingExecutor 中的处理即为二级缓存的处理,在处理完二级缓存后,delegate 会执行 BaseExecutor 类中的方法,BaseExecutor 类负责完成对一级缓存的查询或者清空。这里可能会有点乱,结合下图一起理解:
在这里插入图片描述
在 MyBatis 中,所有的增删改操作,最终都是通过 CachingExecutor 类中的 update 方法来执行的,在 CachingExecutor 中,query() 和 update() 方法统一先去操作一遍二级缓存,然后,在进入 BaseExecutor 中根据操作不同,对一级缓存执行不同的操作。

了解了 MyBatis 在什么时候使用一二级缓存后,接下来分别看看 MyBatis 的一二级缓存是如何实现的。

一级缓存

MyBatis 一级缓存对象的创建,是在初始化 MyBatis 配置时,会创建一个默认的 SimpleExecutor 对象,创建执行器源码如下:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 默认会创建一个 SimpleExecutor 执行器
      executor = new SimpleExecutor(this, transaction);
    }
    // cacheEnabled 默认为 true,为 true 则表示启用二级缓存,只是启用,如果要使用时,还需配置。
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

从该方法中可以看出,无论是否创建 CachingExecutor 执行器(二级缓存),一级缓存均会被使用到。

而一级缓存对象的创建,会在 SimpleExecutor 的构造方法中,其会调用父类的构造方法,由 BaseExecutor 来完成 一级缓存对象的创建。

protected BaseExecutor(Configuration configuration, Transaction transaction) {
    ...
    this.localCache = new PerpetualCache("LocalCache");
    ...
  }

使用的话,主要是在 BaseExecutor 类中的 update(…) 和 query(…) 这两个方法中会被使用到,分为清空和查询。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ...
    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--;
    }
    ...
    return list;
}


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;
}

// 清空一级缓存
@Override
public void clearLocalCache() {
    if (!closed) {
        localCache.clear();
        localOutputParameterCache.clear();
    }
}

再来看 PerpetualCache 类的具体代码:

public class PerpetualCache implements Cache {

  private final String id;
  // 所有的缓存键值对均会存储在 cache 中
  private Map<Object, Object> cache = new HashMap<>();
  
  public PerpetualCache(String id) {
    this.id = id;
  }

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

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  // other Methods...
}

从 PerpetualCache 的关键部分的源码中可以看出,一级缓存其实是以一个 HashMap 来进行键值的存储,整个存取过程完全是对 HashMap 在进行操作。

一级缓存作用域

一级缓存的默认作用域为 session 级别,在 MyBatis 初始化配置环境的时候,会调用 XMLConfigBuilder 的 settingsElement(…) 来进行配置:

XMLConfigBuilder.class

private void settingsElement(Properties props) {
    // other confiuration
    
    // 获取 props 的 localCacheScope 设置值,如果值为空,则将其设置为 SESSION 
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
   	// other configuration
  }

// localCacheScope 共两个可选值:
public enum LocalCacheScope {
  SESSION,STATEMENT
}

具体的作用于效果,我写了一个测试方法,如下:

@Test
public void testL1Cache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    SysUserMapper mapper = sqlSession.getMapper(SysUserMapper.class);
    System.err.println("执行第一遍查询~~~~");
    mapper.selectById(1006);
    System.err.println("执行第二遍查询~~~~");
    mapper.selectById(1006);
    sqlSession.close();
    System.err.println("将原 sqlSesssion 对象关闭,重新开启一个新的 sqlSession1 ");
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SysUserMapper mapper1 = sqlSession1.getMapper(SysUserMapper.class);
    System.err.println("执行第三遍查询~~~~");
    mapper1.selectById(1006);
}

因为 MyBatis 的一级缓存默认作用域为 session 级别,所有,执行上面的测试方法,控制台应该只打印出两次查询的结果。实际打印结果如下:

执行第一遍查询~~~~
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1006(Integer)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1006, 孙悟空, 123456, SWK@xiyouji.com, <<BLOB>>, <<BLOB>>, 2020-06-15 07:46:06
DEBUG [main] - <==      Total: 1
执行第二遍查询~~~~
将原 sqlSesssion 对象关闭,重新开启一个新的 sqlSession1 
执行第三遍查询~~~~
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1006(Integer)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1006, 孙悟空, 123456, SWK@xiyouji.com, <<BLOB>>, <<BLOB>>, 2020-06-15 07:46:06
DEBUG [main] - <==      Total: 1

通过上面的打印结果可以看出:执行第一遍查询时,一级缓存为空,所以,会去查询数据库,并将结果放到一级缓存中;当执行第二遍查询时,由于一级缓存已经有数据,故直接从缓存中拿到数据进行返回,不执行查询数据库操作;执行第三遍查询时,由于关闭原来的 sqlSession ,重新创建的这个 sqlSession 对象中没有缓存,所以,会再次查询数据库。


下一篇来说说二级缓存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值