8、查询缓存(二级缓存)(mybatis笔记)

一、原理

1
  • 第一次SqlSession1去查询用户id为1的用户信息,查询到用户信息将会将查询到的数据存储到二级缓存中。

  • 第二次SqlSession2去查询用户id为1的用户信息,先去缓存中找,如果缓存有则直接从缓存中取数据,如果没有则去数据库中查询。

  • 二级缓存与一级缓存的区别,二级缓存的范围更大,多个SqlSession可以共享一个UserMapper的二级缓存区域。UserMapper有一个二级缓存区域(这个是按照namespace划分),其他mapper也有自己的二级缓存区域(按照namespace划分)。每一个namespacemapper有一个二级缓存区域。也就是说如果两个mappernamespace相同,那么这两个mapper执行sql查询到的数据将存储在一个二级缓存区域中。

  • 当然我们如果在两次查询之间加入提交操作,则同样会清空二级缓存的。这样第二次查询的时候也要发出sql去数据库中查询。

二、测试

  • 开启二级缓存(工程mybatis13
    除了在SqlMapConfig.xml中开启二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。
    SqlMapConfig.xml
<settings>
    <!-- 打开延迟加载的开关,再将积极加载改为消极加载(即按需加载) -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <!-- 开启二级缓存,这里设置是为了方便管理,本身默认是开启的 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

UserMapper.xml

<!-- 开启本mapper的namespace下的二级缓存 -->
<cache />

说明:其实在全局配置文件中配置主要是为了便于管理。在UserMapper.xml中开启二级缓存,UserMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)。

索引 描述 允许值 默认值
cacheEnabled 对在此配置文件下的所有cache进行全局性开/关设置 true/false true
  • 设置pojo类实现序列换接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定在内存中。
    User.java
public class User implements Serializable{
    private Integer id ;
    private String username ;
    private String sex ;
    private Date birthday ;
    private String address ;
    //用户创建的订单列表
    private List<Orders> ordersList ;
....
}
  • 测试
    UserMapperTest.java
//二级缓存测试
@Test
public void testCache2() throws Exception{
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();
    
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);   
    //第一次发起请求,查询id为1的用户
    User user1 = userMapper1.findUserById(1);
    System.out.println(user1);
    sqlSession1.close();//如果不关闭,SqlSession数据是不能写到二级缓存区域的

    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);   
    //第二次发起请求,查询id为1的用户
    User user2 = userMapper2.findUserById(1);
    System.out.println(user1);
    sqlSession2.close();
}

此时的测试结果为:

2

可以看到总共只是发出了一条sql语句,同时注意到有一项信息Cache Hit Retio,这表示缓存命中率,第一次的时候缓存中没有数据则命中率是0.0,第二次先从一级缓存中找,没有;再从二级缓存中找,找到了,此时缓存命中率是0.5。

  • 加入提交
//二级缓存测试
@Test
public void testCache2() throws Exception{
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();
    
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);   
    //第一次发起请求,查询id为1的用户
    User user1 = userMapper1.findUserById(1);
    System.out.println(user1);
    sqlSession1.close();//如果不关闭,SqlSession数据是不能写到二级缓存区域的
    
    //执行提交操作
    UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);   
    User user = userMapper3.findUserById(1);
    user.setUsername("狗蛋");
    userMapper3.updateUser(user);
    sqlSession3.commit();//执行提交清空UserMapper下的二级缓存
    sqlSession3.close();

    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);   
    //第二次发起请求,查询id为1的用户
    User user2 = userMapper2.findUserById(1);
    System.out.println(user1);
    sqlSession2.close();

}

此时我们在测试的时候就会发现发出了两条查询语句。

三、二级缓存其他一些参数

3.1 禁用二级缓存

  • statement中设置userCache="false"可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" userCache = "false">

总结:针对每次查询都需要最新的数据,要设置成禁用二级缓存。

3.2 刷新缓存

  • mapper的同一个namespace中,如果有其它的insert、update、delete操作数据后需要刷新缓存,如果不执行,则缓存中可能出现脏数据。

  • 设置statement中的flushCache="true"属性,默认情况下为true,即刷新缓存,如果改成false则不会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。如下:

<insert id="insertUser" parameterType="User" flushCache="true">

注意:这里刷新缓存就是清空缓存。
总结:一般情况下,执行完commit操作都需要刷新缓存,flushCache="true"表示刷新缓存,这样可以避免数据库脏读。

3.3 Mybatis Cache参数

  • flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形成的时间段。默认情况下,也就是没有刷新问题,缓存仅仅调用语句时刷新。
  • size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024
  • readOnly(只读)属性可以被设置为truefalse。只读的缓存会给所有的调用者返回缓存对象的相同示例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false
    如下例子:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

这个更高级的配置创建了一个FIFO缓存,并每隔60秒刷新,存储结果对象或列表的512个引用,而且返回的对象被认为是只读,因此在不同线程中的调用者之间修改它们会导致冲突。可用的解决策略有,默认的是LRU

  • 1.LRU:最近最少使用的:移除最长时间不被使用的对象
  • 2.FIFO:先进先出:按对象进入缓存的顺序来移除它们
  • 3.SOFT: 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  • 4.WEAK : 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。

3.4 整合EhCache

EhCache是一个分布式的缓存框架。即使我们使用mybatis的二级缓存,查询出来的相关数据也只是保存在单个服务器上,所以我们需要使用分布式的缓存。

  • 导入相关的jar包:(工程mybatis14
ehcache-core-2.6.8.jar
mybatis-ehcache-1.0.3.jar
  • 加入EhCache的配置文件config/ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <diskStore path="E:\ehcache" />
    <defaultCache 
        maxElementsInMemory="10000" 
        eternal="false"
        overflowToDisk="false"
        timeToIdleSeconds="120" 
        timeToLiveSeconds="120" 
        maxElementsOnDisk="10000000"
        diskExpiryThreadIntervalSeconds="120" 
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
  • 在相关的UserMapper.xml文件中进行配置:
<!-- 开启本mapper的namespace下的二级缓存,
type指定cache接口的实现类的接口,mybatis默认使用PerpetualCache类,我们要和
EhCache整合,需要配置type为EhCache的实现cache接口的类 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

之前我们只是开启了二级缓存,默认是使用mybatis的二级缓存,这里我们配置使用EhCache缓存。

  • 测试
    测试文件不需要改动,直接测试即可,我们发现还是可以实现二级缓存的效果。

四、二级缓存的应用场景

  • 对于访问多的查询请求且用户对查询结果实时性要求不高,此时可采用mybatis的二级缓存技术降低数据库的访问量,提高访问速度,业务场景比如:耗时较高的统计分析sql、电话账单查询sql等。

  • 实现方法如下:通过设置刷新间隔时间,由mybatis每隔一段时间自动清空缓存,根据数据变化频率设置缓存刷新间隔flushInterval,比如设置为30分钟、60分钟等,根据需求而定。

五、局限性

Mybatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其他商品的信息,因为mybatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有的商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。

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

推荐阅读更多精彩内容

  • MyBatis--查询缓存 查询缓存的使用,主要是为了提高查询访问速度。将用户对同一数据的重复查询过程简化,不再每...
    我可能是个假开发阅读 3,020评论 3 13
  • 1. 二级缓存的原理 前面介绍了,mybatis中的二级缓存是mapper级别的缓存,值得注意的是,不同的mapp...
    我相信你爱过gg阅读 688评论 0 4
  • MyBatis的配置和使用原理 MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。My...
    王帅199207阅读 910评论 0 4
  • 1 缓存介绍# MyBatis支持声明式数据缓存(declarative data caching)。当一条SQL...
    七寸知架构阅读 2,112评论 2 51
  • 非本人总结的笔记,抄点笔记复习复习。感谢传智博客及黑马程序猿成长 关联查询 数据中的表结构 数据库的分析方法 第一...
    键盘瞎阅读 1,092评论 3 5