Mybatis 缓存

Mybatis是什么?

Mybatis是一个半自动 ORM(对象关系映射)框架,它内部封装了JDBC,加载驱动、创建连接、创建 statement 等繁杂的过程,开发的时候只需要关注如何编写 SQL 语句,而不用关心其他的。
为什么说 Mybatis 是一个半自动 ORM 的框架呢?
ORM,是Object和Relation之间的映射,而Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 框架,而Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
这也是为什么有些面试官在面试初级程序员的时候,很喜欢说,Mybatis,和 Hibernate 都有什么优缺点,为啥选择使用的 Mybatis 而不选择使用 Hibernate 呢?

Mybatis的一级、二级缓存

Mybatis 的一级缓存

先说 Mybatis 的一级缓存,因为这是如果不手动配置,他是自己默认开启的一级缓存,一级缓存只是相对于同一个 SqlSession 而言,参数和SQL完全一样的情况下,使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL,因为使用SelSession第一次查询后,MyBatis会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库。
来画个图表示一下一级缓存
2021-05-31-15-32-44-989511.png
那面试官肯定会说,直接从数据库查不就行了,为啥要一级缓存呢?
当使用MyBatis开启一次和数据库的会话时,MyBatis 会创建出一个 SqlSession 对象表示一次与数据库之间的信息传递,在执行 SQL 语句的过程中,可能会反复执行完全相同的查询语句,如果不采取一些措施,每一次查询都会查询一次数据库,而如果在极短的时间内做了很多次相同的查询操作,那么这些查询返回的结果很可能相同。
也就是说,如果在短时间内,频繁的去执行一条 SQL,查询返回的结果本来应该是改变了,但是查询出来的时候,会出现结果一致的情况,正是为了解决这种问题,也为了减轻数据库的开销,所以 Mybatis 默认开启了一级缓存。

Mybatis 的二级缓存

Mybatis 的二级缓存一般如果不对他进行设置,他是不会开启的,而二级缓存是什么呢?Mybatis 中的二级缓存实际上就是 mapper 级别的缓存,而这时候肯定会有人说,那么不同之间的 Mapper 是同一个缓存么?
答案是否定的,他不是一个,Mapper 级别的缓存实际上就是相同的 Mapper 使用的是一个二级缓存,但是在二级缓存中,又有多个不同的 SqlSession,而不同的 Mapper 之间的二级缓存也就是互相不会影响的。
就类似下面的图
2021-05-31-15-32-45-066274.png
这二级缓存是不是就看起来有点意思了?

开启二级缓存

  1. MyBatis 配置文件

    1. <settings>
    2. <setting name = "cacheEnabled" value = "true" />
    3. </settings>
  2. MyBatis 要求返回的 POJO 必须是可序列化的

  3. Mapper 的 xml 配置文件中加入 标签

既然想要了解这个二级缓存,那么必然,还得知道它里面的配置都有哪些含义。
先从标签看起,然后从源码里面看都有哪些配置信息提供给开发者使用:
2021-05-31-15-32-45-121129.png
blocking:直译就是调度,而在 Mybatis 中,如果缓存中找不到对应的 key ,是否会一直 blocking,直到有对应的数据进入缓存。
eviction:缓存回收策略

缓存回收策略

而缓存回收策略,在源码中是有直接体现的,那么分别都对应了什么形式呢?

  1. typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  2. typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  3. typeAliasRegistry.registerAlias("LRU", LruCache.class);
  4. typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  5. typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
  • PERPETUAL:选择 PERPETUAL 来命名缓存,暗示这是一个最底层的缓存,数据一旦存储进来,永不清除。好像这种缓存不怎么受待见。
  • FIFO:先进先出:按对象进入缓存的顺序来移除它们
  • LRU:最近最少使用的:移除最长时间不被使用的对象。
  • SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

虽然看着 PERPETUAL 排在了第一位,但是它可不是默认的,在 Mybatis 的缓存策略里面,默认的是 LRU 。
PERPETUAL:
源代码如下:

  1. public class PerpetualCache implements Cache {
  2. private final String id;
  3. private Map<Object, Object> cache = new HashMap<>();
  4. public PerpetualCache(String id) {
  5. this.id = id;
  6. }
  7. }

看着是不是有点眼熟,它怎么就只是包装了 HashMap ?
既然使用 HashMap,那么必然就会有Key,那么他们的Key是怎么设计的?
CacheKey:

  1. public class CacheKey implements Cloneable, Serializable {
  2. private static final long serialVersionUID = 1146682552656046210L;
  3. public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
  4. private static final int DEFAULT_MULTIPLYER = 37;
  5. private static final int DEFAULT_HASHCODE = 17;
  6. private final int multiplier;
  7. private int hashcode; //用于表示CacheKey的哈希码
  8. private long checksum; //总和校验,当出现复合key的时候,分布计算每个key的哈希码,然后求总和
  9. private int count;//当出现复合key的时候,计算key的总个数
  10. // 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this is not always true and thus should not be marked transient.
  11. private List<Object> updateList;//当出现复合key的时候,保存每个key
  12. }

至于内部如何初始化,如何进行操作,有兴趣的可以去阅读一下源码,导入个源码包,打开自己看一下。

FIFO:先进先出缓冲淘汰策略
  1. public class FifoCache implements Cache {
  2. private final Cache delegate; //被装饰的Cache对象
  3. private final Deque<Object> keyList;//用于记录key 进入缓存的先后顺序
  4. private int size;//记录了缓存页的上限,超过该值需要清理缓存(FIFO)
  5. public FifoCache(Cache delegate) {
  6. this.delegate = delegate;
  7. this.keyList = new LinkedList<>();
  8. this.size = 1024;
  9. }
  10. }

在 FIFO 淘汰策略中使用了 Java 中的 Deque,而 Deque 一种常用的数据结构,可以将队列看做是一种特殊的线性表,该结构遵循的先进先出原则。Java中,LinkedList实现了Queue接口,因为LinkedList进行插入、删除操作效率较高。
看完这个源码的时候,是不是就感觉源码其实也没有那么难看懂,里面都是已经掌握好的知识,只不过中间做了一些操作,进行了一些封装。

LRU:最近最少使用的缓存策略

需要看的源码则是在 Mybatis 中的源码,

  1. public class LruCache implements Cache {
  2. private final Cache delegate;
  3. private Map<Object, Object> keyMap;
  4. private Object eldestKey;//记录最少被使用的缓存项key
  5. public LruCache(Cache delegate) {
  6. this.delegate = delegate;
  7. setSize(1024);//重新设置缓存的大小,会重置KeyMap 字段 如果到达上限 则更新eldestKey
  8. }
  9. public void putObject(Object key, Object value) {
  10. delegate.putObject(key, value);
  11. // 删除最近未使用的key
  12. cycleKeyList(key);
  13. }
  14. }

SOFT:基于垃圾回收器状态和软引用规则的对象
  1. public class SoftCache implements Cache {
  2. //在SoftCache 中,最近使用的一部分缓存项不会被GC回收,这就是通过将其value添加到
  3. private final Deque<Object> hardLinksToAvoidGarbageCollection;
  4. //引用队列,用于记录GC回收的缓存项所对应的SoftEntry对象
  5. private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  6. //底层被修饰的Cache 对象
  7. private final Cache delegate;
  8. //连接的个数,默认是256
  9. private int numberOfHardLinks;
  10. public SoftCache(Cache delegate) {
  11. this.delegate = delegate;
  12. this.numberOfHardLinks = 256;
  13. this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
  14. this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
  15. }
  16. public void putObject(Object key, Object value) {
  17. // 清除被GC回收的缓存项
  18. removeGarbageCollectedItems();
  19. // 向缓存中添加缓存项
  20. delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  21. }
  22. public Object getObject(Object key) {
  23. Object result = null;
  24. // 查找对应的缓存项
  25. @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
  26. SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
  27. if (softReference != null) {
  28. result = softReference.get();
  29. // 已经被GC 回收
  30. if (result == null) {
  31. // 从缓存中清除对应的缓存项
  32. delegate.removeObject(key);
  33. } else {
  34. // See #586 (and #335) modifications need more than a read lock
  35. synchronized (hardLinksToAvoidGarbageCollection) {
  36. hardLinksToAvoidGarbageCollection.addFirst(result);
  37. if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
  38. hardLinksToAvoidGarbageCollection.removeLast();
  39. }
  40. }
  41. }
  42. }
  43. return result;
  44. }
  45. public void clear() {
  46. synchronized (hardLinksToAvoidGarbageCollection) {
  47. // 清理强引用集合
  48. hardLinksToAvoidGarbageCollection.clear();
  49. }
  50. // 清理被GC回收的缓存项
  51. removeGarbageCollectedItems();
  52. delegate.clear();
  53. }
  54. //其中指向key的引用是强引用,而指向value的引用是弱引用
  55. private static class SoftEntry extends SoftReference<Object> {
  56. private final Object key;
  57. SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
  58. super(value, garbageCollectionQueue);
  59. this.key = key;
  60. }
  61. }
  62. }

WEAK:基于垃圾收集器状态和弱引用规则的对象
  1. public class WeakCache implements Cache {
  2. private final Deque<Object> hardLinksToAvoidGarbageCollection;
  3. private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
  4. private final Cache delegate;
  5. private int numberOfHardLinks;
  6. public WeakCache(Cache delegate) {
  7. this.delegate = delegate;
  8. this.numberOfHardLinks = 256;
  9. this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
  10. this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
  11. }
  12. }

WeakCache在实现上与SoftCache几乎相同,只是把引用对象由SoftReference软引用换成了WeakReference弱引用。