Mybatis二级缓存


Mybatis的二级缓存和一级缓存作用一样,都是希望在执行查询操作时,减少对数据库(磁盘)中数据的访问,增加对缓存(内存)中数据的访问,从而提高查询接口的性能。

与一级缓存不一样的是:一级缓存是SeqSession级别的,每一个SeqSession对应一个一级缓存;二级缓存是Mapper级别的,一个二级缓存会对应一个或多个XXXMapper.xml文件。

使用二级缓存

对于一级缓存默认是开启的,二级缓存需要开发人员手动开启,指定某一条select查询Sql使用二级缓存方式:

  1. mybatis-config中的cacheEnabled为true,该属性默认就是true,可以不用显式配置

    <settings>
         <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. xxxMapper.xml配置文件中添加<cache/>标签

  3. select查询语句中将useCache置为true

    <select id="selectList" useCache="true">
    </select>
    
  4. 该select查询语句执行完成后需要提交事务

测试代码:

@Test
public void test2() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<User> users = sqlSession.selectList("edu.hzb.dao.UserDAO.selectList");
    //        sqlSession.commit();
    System.out.println("------------------------------");
    List<User> users1 = sqlSession.selectList("edu.hzb.dao.UserDAO.selectList");

}

测试代码日志:

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

因为第一次select查询sql执行完后没有提交事务,导致查询的数据没有存放在二级缓存中。第二次执行相同的查询sql就没有从二级缓存中获取到数据,但是从一级缓存中获取到了之前的查询结果,所以看到只打印了一次查询数据库日志。

测试代码:

@Test
public void test2() throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<User> users = sqlSession.selectList("edu.hzb.dao.UserDAO.selectList");
    sqlSession.commit();
    System.out.println("------------------------------");
    List<User> users1 = sqlSession.selectList("edu.hzb.dao.UserDAO.selectList");
}

测试代码日志:

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

第一次执行查询sql语句完后提交事务,查询的结果就由TransactionalCacheManager对象进行管理,等到事务提交后,就会把数据存放在二级缓存中。

二级缓存流程

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

CachingExecutor是对Executor进行增强,为Executor增加二级缓存的功能。看CachingExecutor的属性就可以知道:

public class CachingExecutor implements Executor {

  //被增强的Executor
  private final Executor delegate;
  //管理TransactionalCache对象
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
}
public class TransactionalCacheManager {
  //key为二级缓存对象
  //TransactionCache是装饰器对象,对Cache进行增强
  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
}
public class TransactionalCache implements Cache {
  //被增强的Cache
  private final Cache delegate;
  //提交事务时,清空缓存的标识
  private boolean clearOnCommit;
  //待提交的数据(只有在事务提交时,才会将数据存放在二级缓存中)
  private final Map<Object, Object> entriesToAddOnCommit;
  //缓存中没有命中的数据
  private final Set<Object> entriesMissedInCache;
}

CacheExecutor中的方法:

  1. 查询方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //查询sql以及参数
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //缓存的key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //执行查询方法
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 		ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //二级缓存
    Cache cache = ms.getCache();
    //开启了二级缓存
    if (cache != null) {
      //是否需要清空entriesToAddOnCommit中的数据
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //从二级缓存中获取数据
        List<E> list = (List<E>) tcm.getObject(cache, key);
        //没有命中
        if (list == null) {
          //查询一级缓存或数据库
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //将查询到的数据存放在entriesToAddOnCommit中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //没有开启二级缓存,查询一级缓存或数据库
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
  1. 增删改方法
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    //是否需要清空entriesToAddOnCommit中的数据
    flushCacheIfRequired(ms);
    //执行被增强的Executor中的update方法
    return delegate.update(ms, parameterObject);
}
  1. commit方法
public void commit(boolean required) throws SQLException {
    //被增强的Executor的commit方法
    delegate.commit(required);
    tcm.commit();
}

CacheingExecutorExecutor的commit方法增强的功能体现在tcm.commit()方法中,进入到到该方法中:

public void commit() {
  for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
  }
}

public void commit() {
    //提交事务清空缓存的标识
    if (clearOnCommit) {
      delegate.clear();
    }
    //将待提交的数据真正存放在二级缓存中,这行代码就可以知道只有在事务提交时才会将查询到的结果存放在二级缓存	//中,下一次执行相同的查询sql,二级缓存才会命中
    flushPendingEntries();
    //清空entriesToAddOnCommit和entriesMissedInCache
    reset();
}

private void flushPendingEntries() {
    //将待提交的数据真正存放在二级缓存中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    //缓存中没有命中的数据
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        //防止缓存穿透
        delegate.putObject(entry, null);
      }
    }
}

二级缓存的选择

MyBatis对二级缓存的设计非常灵活,除了自己内部实现了一系列的Cache缓存实现类。开发人员还可以自己定义缓存的实现,或者与第三方缓存进行集成。但是要实现Mybatis提供的Cache接口中的方法,并且在<cache type=""/>的type属性中指定Cache接口的实现类。

Mybatis的Cache接口:

自定义或第三方缓存实现了Cache接口中的方法,这样Mybatis在调用Cache接口中的方法时,就会回调自定义或第三方缓存接口实现。

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;
public interface Cache {
  String getId();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  Object removeObject(Object key);
  void clear();
  int getSize();
  ReadWriteLock getReadWriteLock();
}

xxxMapper.xml文件中指定<cache/>标签的type属性:

<!-- 整合EhCache-->
<!--    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>-->
<!-- 整合OSCache-->
<!--    <cache type="org.mybatis.caches.oscache.OSCache"/>-->
<!-- 整合Redis作为Cache-->
<!--    <cache type="org.mybatis.caches.redis.RedisCache" />-->
<cache/>

所以Mybatis二级缓存的选择包含:

  1. MyBatis自身提供的缓存实现
  2. 用户自定义的Cache接口实现
  3. 第三方缓存,比如EhCache、OSCache、Redis

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

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MyBatis二级缓存是一种在多个SqlSession之间共享缓存数据的机制。它是在SqlSessionFactory级别上进行缓存的,可以跨多个SqlSession共享缓存数据。二级缓存的本质是将查询结果存储在内存中,以提高查询效率。 要启用MyBatis二级缓存,需要在MyBatis的配置文件中设置<setting name="cacheEnabled" value="true" />。默认情况下,二级缓存是关闭的,需要手动开启。 值得注意的是,使用MyBatis二级缓存时,返回的POJO必须是可序列化的,因为缓存需要将数据序列化到内存中。 需要注意的是,二级缓存是基于namespace级别的,不同的namespace拥有独立的二级缓存。当在一个namespace中进行数据修改时,会自动刷新该namespace下的所有查询缓存。而当在一个namespace中进行数据修改时,其他namespace下的查询缓存不会被刷新。因此,在使用二级缓存时,需要注意数据的一致性和缓存的刷新机制。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【MyBatisMyBatis 二级缓存全详解](https://blog.csdn.net/qq_21383435/article/details/124768956)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [Mybatis二级缓存](https://blog.csdn.net/weixin_52851967/article/details/125190163)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值