Mybatis一级缓存

缓存目的

缓存的目的是为了解决速度不匹配问题。内存的访问速度远远超过磁盘的访问速度,所以为了减少磁盘的IO次数所带来的时间消耗,很多场景下都会选择在内存中开辟一块比较小的区域充当缓存。

Mybatis缓存

Mybatis缓存机制是在执行一条查询SQL语句后,会将查询到的结果集以键值对的方式存储起来,等到下一次执行相同的查询SQL时,会先尝试从缓存中读取数据,如果从缓存中读取到了对应的结果,则直接返回;否则查询数据库,并且将查询到的结果放置在缓存中。

Mybatis缓存分为一级缓存和二级缓存:

一级缓存:SQLSession级别缓存,每次MyBatis开启一次和数据库的会话,就会创建出一个SqlSession对象表示一次数据库会话。所以一个一级缓存对应一个SqlSession。

二级缓存:Mapper级别缓存。对于二级缓存的开启条件之一就是需要在XXXMapper.xml中书写<cache/>标签。所以一个二级缓存对应一个或多个XXXMapper.xml文件。(通过<cache-ref/>标签设置多个XXXMapper.xml文件共用同一个二级缓存)

Mybatis一级缓存概述

Mybatis一个SqlSession对象中创建一个本地缓存localCache,在二级缓存不命中情况下,对于每一次查询,都会尝试去本地缓存中查找当前查询Sql的结果数据,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户。

Mybatis查询数据顺序:二级缓存 ---> 一级缓存 ---> 数据库

一级缓存是SQLSession级别的,每一个SqlSession会创建一个一级缓存,两个不同的SqlSession执行同一个查询Sql也是查询各自的一级缓存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JVImWjG6-1684509044298)(res/Mybatis一级缓存/image-20230514101637248.png)]

一级缓存工作流程

下图是查询一级缓存的流程,重点看一下Cache接口:

  1. 根据SQLSession执行查询Sql,Executor会根据Sql语句、查询参数等内容创建一个key值,根据key值查询一级缓存;

    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    
  2. 根据key值就会从Cache(PerpetualCache)中获取对应的缓存结果;

    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    
  3. 如果命中,直接将缓存结果返回;

  4. 如果不命中,则会查询数据库,并且将查询到的数据存放在缓存中,最后返回结果;

    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);
      //执行Sql都是采用PreparedStatement,可以忽略下面的if语句块
      if (ms.getStatementType() == StatementType.CALLABLE) {
        localOutputParameterCache.putObject(key, parameter);
      }
      //返回结果
      return list;
    }
    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TDxCXUQ1-1684509044301)(res/Mybatis一级缓存/image-20230514103749342.png)]

Cache接口

public interface Cache {
  //Cache的id值,用于唯一标识Cache
  String getId();
  //向缓存中添加数据,键值对结构的
  void putObject(Object key, Object value);
  //根据key,获取缓存对应的数据
  Object getObject(Object key);
  //根据key,删除对应的缓存数据
  Object removeObject(Object key);
  //清空缓存 
  void clear();
  //缓存大小
  int getSize();
  
  //读写锁,不用该方法,Cache接口的实现类也没有实现该方法
  ReadWriteLock getReadWriteLock();

}

PerpetualCache

对于Cache接口,Mybatis提供了很多的实现类,其中除了perpetualCache,其他的实现类都属于对Cache的装饰器类。一级缓存也是通过PerpetualCache实现的:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xI5O0Jgl-1684509044305)(res/Mybatis一级缓存/image-20230514105049559.png)]

进入到PerpetualCache中,也只是采用HashMap充当缓存:

public class PerpetualCache implements Cache {

  private final String id;

  //通过Map充当缓存,key和value都是Object类型
  private Map<Object, Object> cache = new HashMap<Object, Object>();

  //通过构造方法唯一标识Cache的id
  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

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

  //并没有实现获取读写锁方法
  @Override
  public ReadWriteLock getReadWriteLock() {
    return null;
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

这里采用HashMap充当一级缓存,没有限制Map的大小,是因为SqlSession的生存时间比较短,不会向Map中存放过多的缓存数据,当SqlSession消亡时,对应的一级缓存也会被清空;另外执行insert|update|delete修改操作时也会清空一级缓存。

Cache的装饰器

Cache接口的实现类中,除了PerpetualCache类,其他的实现类都属于装饰器类:

  • FifoCache:先进先出算法,如果缓存中的容量已经满了,那么会将最先进入缓存中的数据清除掉

  • LruCache:最近最少使用算法,即如果缓存中容量已经满了,会将缓存中最近最少被使用的缓存记录清除掉

  • LoggingCache:为Cache增加日志功能,调用getObject(Object key)方法时打印日志

  • ScheduledCache:指定的某一个时间间隔将Cache缓存中的数据清空

  • SerializedCache:添加序列化功能,在调用getObject(Object key)和putObject(Object key, Object value)方法时对key和value进行序列化和反序列化功能。

    MybatisRedis整合时,Redis重写了Cache接口中的方法,其中putObject和getObject就对key和value进行了序列化和反序列化操作。虽然Redis中的key一般都是String类型,但是Mybatis缓存中的key是一个CacheKey对象,并不是一个字符串。而且对key和value进行序列化,也可以节省缓存空间。
    
  • SynchronizedCache:为Cache中的方法加上了Synchronized关键字,实现了不同线程对Cache的同步

  • TransactionalCache:针对二级缓存,只有在事务提交后,才会将结果存放在二级缓存中。

这些装饰器可以对指定的Cache进行增强,例如:

//核心Cache
PerpetualCache perpetualCache = new PerpetualCache("123");
//通过装饰器模式,为Cache增加功能
//增加日志功能
LoggingCache loggingCache = new LoggingCache(perpetualCache);
//增加LRU算法
LruCache lruCache = new LruCache(loggingCache);
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值