mybatis缓存机制

一级缓存

定义:一级缓存也叫本地缓存,是在会话(SqlSession)层面进行缓存的。一级缓存是默认打开的,并没有任何设置或判断语句控制是否执行一级缓存查询、添加操作。所以,无法关闭掉一级缓存。

在DefaultSqlSession中,Executor接口实现类BaseExecutor里有个缓存对象PerpetualCache localCache。

public class PerpetualCache implements Cache {

  // 对象id,如LocalCache
  private final String id;

  /**
   * 缓存map
   * key为createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql)生成
   * value为数据库查询出的结果集List<E>
   *
   */
  private Map<Object, Object> cache = new HashMap<>();
}

在同一个会话里面,多次执行相同的SQL 语句,会直接从内存取到缓存的结果,不会再发送SQL 到数据库。但是不同的会话里面,即使执行的SQL 一模一样(通过一个Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。

一级缓存的工作流程

  1. 对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果。
  2. 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中。
  3. 如果命中,则直接将缓存结果返回。
  4. 如果没命中,则去数据库中查询数据,得到查询结果;将key和查询到的结果分别作为key,value对存储到Cache中;将查询结果返回。

一级缓存的生命周期

  1. MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
  2. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
  3. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
  4. SqlSession中执行了任何一个update操作(update()、delete()、insert()),都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

一级缓存范围

MyBatis 一级缓存(MyBaits 称其为 Local Cache)无法关闭,但是有两种级别可选:

  1. session 级别的缓存,在同一个 sqlSession 内,对同样的查询将不再查询数据库,直接从缓存中。
  2. statement 级别的缓存,避坑: 为了避免这个问题,可以将一级缓存的级别设为 statement 级别的,这样每次查询结束都会清掉一级缓存。
<!-- MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
             默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
            若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。  -->
<setting name="localCacheScope" value="SESSION"/>
      

一级缓存的不足

使用一级缓存的时候,因为缓存不能跨会话共享,不同的会话之间对于相同的数据可能有不一样的缓存。在有多个会话或者分布式环境下,会存在脏数据的问题。如果要解决这个问题,就要用到二级缓存。

二级缓存

定义:范围是namespace级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。

在DefaultSqlSession中,Executor接口实现类CachingExecutor里有个缓存对象TransactionalCacheManager tcm。

/**
 * 用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionalCaches字段
 */
public class TransactionalCacheManager {
   /**
    *   二级缓存map,key和value与一级缓存map一样
   */
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
  
  /**
   * CachingExecutor调用此方法,用key查询返回value,
   * @param cache
   * @param key
   * @return
   */
  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }
  
  /**
   * map.computeIfAbsent(K key,
   *             Function<? super K, ? extends V> mappingFunction)
   * key 就是我们要用来做映射的key
   * mappingFunction 就是我们的映射函数
   * 这个方法返回当前与key关联的值(无论是之前已经存在的,或者是刚刚计算出来的),可能为null。
   * TransactionalCache::new, 调用TransactionalCache的构造方法
   * @param cache
   * @return
   */
  private TransactionalCache getTransactionalCache(Cache cache) {
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }
  }
}

缓存对象TransactionalCacheManager里的TransactionalCache对象

public class TransactionalCache implements Cache {
//对应的二级缓存对象
  private final Cache delegate;
  //是否在commit时清除缓存
  private boolean clearOnCommit;
  //需要在commit时提交到二级缓存的数据
  private final Map<Object, Object> entriesToAddOnCommit;
  //缓存未命中的数据,事务commit时,也会放入二级缓存(key,null)
  private final Set<Object> entriesMissedInCache;
}

开启二级缓存方法

1.全局缓存开关:mybatis配置文件中声明

<!-- mybatis-config.xml 开启二级缓存(默认是开的,这里写出来是为了方便代码维护) -->
<setting name="cacheEnabled" value="true" />

2.各个namespace下的二级缓存实例 mapper.xml

<!--Mapper.xml 中配置<cache/>标签 namespace同级  -->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
    size="1024"
eviction="LRU"
flushInterval="120000"
readOnly="false"/>

或引用其他namespace的缓存

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

Mapper.xml 配置了<cache>之后,select()会被缓存。update()、delete()、insert()会刷新缓存。如果cacheEnabled=true,Mapper.xml没有配置标签,则不会有二级缓存,但只要配置cacheEnabled=true,基本执行器就会被装饰,还是会出现CachingExecutor包装对象。

3.<select>节点中配置useCache属性
默认为true,设置false时,二级缓存针对该条select语句不会生效

缓存的清除策略

1.LRU(mybatis默认) – 最近最少使用
Least Recently Used,移除最长时间不被使用的对象。
2.LFU - 一定时期内被访问次数最少的
Least Frequently Used,移除某段时间内被访问次数最少的
3.FIFO – 先进先出
按对象进入缓存的顺序来移除它们。
4.SOFT – 软引用
基于垃圾回收器状态和软引用规则移除对象。
5.WEAK – 弱引用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 222,104评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,816评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,697评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,836评论 1 298
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,851评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,441评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,992评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,899评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,457评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,529评论 3 341
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,664评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,346评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,025评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,511评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,611评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,081评论 3 377
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,675评论 2 359