MyBatis一级缓存详解(二)

简介: 缓存就是内存中的一个对象,用于对数据库查询结果的保存,用于减少与数据库的交互次数从而降低数据库的压力,进而提高响应速度。

一级缓存原理探究


一级缓存到底是什么?一级缓存的工作流程是怎样的?一级缓存何时消失?相信你现在应该会有这几个疑问,那么我们本节就来研究一下一级缓存的本质


嗯。。。。。。该从何处入手呢?


你可以这样想,上面我们一直提到一级缓存,那么提到一级缓存就绕不开 SqlSession,所以索性我们就直接从 SqlSession ,看看有没有创建缓存或者与缓存有关的属性或者方法

60.jpg调研了一圈,发现上述所有方法中,好像只有 `clearCache()` 和缓存沾点关系,那么就直接从这个方法入手吧,分析源码时,**我们要看它(此类)是谁,它的父类和子类分别又是谁**,对如上关系了解了,你才会对这个类有更深的认识,分析了一圈,你可能会得到如下这个流程图

61.jpg

再深入分析,流程走到Perpetualcache 中的 clear() 方法之后,会调用其 cache.clear() 方法,那么这个cache 是什么东西呢? 点进去发现,cache 其实就是 private Map<Object, Object> cache = new HashMap<Object, Object>(); 也就是一个Map,所以说 cache.clear() 其实就是 map.clear() ,也就是说,缓存其实就是本地存放的一个 map 对象,每一个SqlSession 都会存放一个 map 对象的引用,那么这个 cache 是何时创建的呢?


你觉得最有可能创建缓存的地方是哪里呢? 我觉得是 Executor,为什么这么认为? 因为 Executor 是执行器,用来执行SQL请求,而且清除缓存的方法也在 Executor 中执行,所以很可能缓存的创建也很有可能在 Executor 中,看了一圈发现 Executor 中有一个 createCacheKey 方法,这个方法很像是创建缓存的方法啊,跟进去看看,你发现 createCacheKey 方法是由 `BaseExecutor` 执行的,代码如下

CacheKey cacheKey = new CacheKey();
//MappedStatement的id
// id 就是Sql语句的所在位置 包名 + 类名 + SQL名称
cacheKey.update(ms.getId());
// offset 就是 0
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
// 具体的SQL语句
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
  // issue #176
  cacheKey.update(configuration.getEnvironment().getId());
}

创建缓存key会经过一系列的 update 方法,update 方法由一个 CacheKey 这个对象来执行的,这个 update 方法最终由 updateList 的 list 来把五个值存进去,对照上面的代码和下面的图示,你应该能理解这五个值都是什么了

62.jpg


这里需要注意一下最后一个值, configuration.getEnvironment().getId() 这是什么,这其实就是定义在
mybatis-config.xml 中的 <environment> 标签,见如下。

<environments default="development">
 <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
      <property name="driver" value="${jdbc.driver}"/>
     <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
    </dataSource>
  </environment>
></environments>

那么我们回归正题,那么创建完缓存之后该用在何处呢?总不会凭空创建一个缓存不使用吧?绝对不会的,经过我们对一级缓存的探究之后,我们发现一级缓存更多是用于查询操作毕竟一级缓存也叫做查询缓存吧,为什么叫查询缓存我们一会儿说。我们先来看一下这个缓存到底用在哪了,我们跟踪到 query 方法如下:

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

如果查不到的话,就从数据库查,在queryFromDatabase中,会对localcache进行写入。localcache 对象的put 方法最终交给 Map 进行存放

private Map<Object, Object> cache = new HashMap<Object, Object>();
@Override
public void putObject(Object key, Object value) {
  cache.put(key, value);
}

那么再说一下为什么一级缓存也叫做查询缓存呢?


我们先来看一下 update 更新方法,先来看一下 update 的源码

@Override
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);
}

BaseExecutor 在每次执行 update 方法的时候,都会先 clearLocalCache() ,所以更新方法并不会有缓存,这也就是说为什么一级缓存也叫做查询缓存了,这也就是为什么我们没有探究多次执行更新方法对一级缓存的影响了。


还有其他要补充的吗?


我们上面分析了一级缓存的执行流程,为什么一级缓存要叫查询缓存以及一级缓存组成条件


那么,你可能看到这感觉这些知识还是不够连贯,那么我就帮你把 `一级缓存的探究 `小结中的原理说一下吧,为什么一级缓存会失效


1. 探究更新对一级缓存失效的影响: 由上面的分析结论可知,我们每次执行 update 方法时,都会先刷新一级缓存,因为是同一个 SqlSession, 所以是由同一个 Map 进行存储的,所以此时一级缓存会失效

2. 探究不同的 SqlSession 对一级缓存的影响: 这个也就比较好理解了,因为不同的 SqlSession 会有不同的Map 存储一级缓存,然而 SqlSession 之间也不会共享,所以此时也就不存在相同的一级缓存

3. 同一个 SqlSession 使用不同的查询操作: 这个论点就需要从缓存的构成角度来讲了,我们通过 cacheKey 可知,一级缓存命中的必要条件是两个 cacheKey 相同,要使得 cacheKey 相同,就需要使 cacheKey 里面的值相同,也就是

63.jpg64.jpg

看出差别了吗?第一个SQL 我们查询的是部门编号为1的值,而第二个SQL我们查询的是编号为5的值,两个缓存对象不相同,所以也就不存在缓存。


4. 手动清理缓存对一级缓存的影响: 由程序员自己去调用clearCache方法,这个方法就是清除缓存的方法,所以也就不存在缓存了。


总结


所以此文章到底写了点什么呢?抛给你几个问题了解一下


1. 什么是缓存?什么是 MyBatis 缓存?


2. 认识MyBatis缓存,MyBatis 一级缓存的失效方式

3. MyBatis 一级缓存的执行流程,MyBatis 一级缓存究竟是什么?

相关文章
|
8月前
|
缓存 Java 数据库连接
Mybatis缓存相关面试题有多卷
使用 MyBatis 缓存机制需要注意以下几点: 对于频繁更新和变动的数据,不适合使用缓存。 对于数据的一致性要求比较高的场景,不适合使用缓存。 如果配置了二级缓存,需要确保缓存的数据不会影响到其他业务模块的数据。 在使用缓存时,需要注意缓存的命中率和缓存的过期策略,避免缓存过期导致查询性能下降。
125 0
|
4月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
1月前
|
缓存 Java 数据库连接
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
32 1
|
1月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
60 4
|
2月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
2月前
|
SQL 缓存 Java
MyBatis如何关闭一级缓存(分注解和xml两种方式)
MyBatis如何关闭一级缓存(分注解和xml两种方式)
86 5
|
7月前
|
SQL 缓存 Java
MYBATIS缓存
MYBATIS缓存
|
3月前
|
缓存 Java 数据库连接
使用MyBatis缓存的简单案例
MyBatis 是一种流行的持久层框架,支持自定义 SQL 执行、映射及复杂查询。本文介绍了如何在 Spring Boot 项目中集成 MyBatis 并实现一级和二级缓存,以提高查询性能,减少数据库访问。通过具体的电商系统案例,详细讲解了项目搭建、缓存配置、实体类创建、Mapper 编写、Service 层实现及缓存测试等步骤。
|
6月前
|
SQL 缓存 Java
【面试官】Mybatis缓存有什么问题吗?
面试官:你说下对MyBatis的理解?面试官:那SqlSession知道吧?面试官:Mybatis的缓存有哪几种?面试官:那Mybatis缓存有什么问题吗?面试官:Mybatis分页插件是怎么
【面试官】Mybatis缓存有什么问题吗?
|
6月前
|
缓存 算法 Java
关于MyBatis的缓存详解
MyBatis 的缓存机制非常灵活,可以通过简单的配置来满足不同的性能需求。合理地使用缓存可以显著提高应用程序的性能,尤其是在处理大量数据库查询时。然而,开发者需要注意缓存的一致性和并发问题,特别是在使用可读写缓存时。