mybatis缓存机制


前言

  学习了mybatis缓存机制,现对其进行总结。


一、mybatis中一级缓存和二级缓存及其区别?

  Mybatis 一级缓存的作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的 一级缓存也就不存在了。Mybatis 默认开启一级缓存。
   Mybatis 二级缓存是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同 的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行 相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从 缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存 需要在 setting 全局参数中配置开启二级缓存。

区别:
1.一级缓存的作用域是一个sqlsession内;二级缓存作用域是针对mapper进行缓存;
2.一级缓存是默认开启的;二级缓存需要手动配置

二、一级缓存的特点

在这里插入图片描述

  一级缓存区域是根据 SqlSession 为单位划分的。 每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。 Mybatis 内部存储缓存使用一个 HashMap,key 为 hashCode+sqlId+Sql 语句。value 为 从查询出来映射生成的 java 对象 sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域

使用一级缓存

    @Test
    public  void Test(){
        SqlSession sqlSession = MybatisUtils.openSession();
        CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
        Customer customer = customerMapper.getCustomerWithId(2);
        System.out.println("Customer1----"+customer);
        Customer customer2 = customerMapper.getCustomerWithId(2);
        System.out.println("Customer1----"+customer2);
        System.out.println("是否相等:"+(customer2==customer));
        sqlSession.close();
    }

结果图:
在这里插入图片描述

  至于上图显示的命中率为0,是因为我们配置开启了二级缓存,显示的是二级缓存的命中率。
  查询的顺序: 二级缓存 => 一级缓存 => 数据库

关闭二级缓存查询结果显示:
在这里插入图片描述

二级缓存的特点

执行流程:
在这里插入图片描述
开启二级缓存
mybatis主配置文件中加入以下代码:

  <setting name="cacheEnabled" value="true"/>

然后在对应的mapper.xml里面加入配置

    <cache eviction="FIFO" flushInterval="6000" readOnly="false" size="50" >

    </cache>

在这里插入图片描述
下图对cache的属性进行介绍
在这里插入图片描述

  由图可知 二级缓存是有设置定时情况缓存的时间的 也有设置对应的回收策略 这也回答了上面的疑问(二级缓存什么时候失效)

二级缓存代码测试
在这里插入图片描述
使用动态代理的方式

   @Test
    public  void Test2(){
        SqlSession sqlSession = MybatisUtils.openSession();
        CustomerMapper customerMapper = sqlSession.getMapper(CustomerMapper.class);
        Customer customer = customerMapper.getCustomerWithId(2);
        System.out.println("customer=="+customer);
        sqlSession.close();

        SqlSession sqlSession2= MybatisUtils.openSession();
        CustomerMapper customerMapper2 = sqlSession2.getMapper(CustomerMapper.class);
        Customer customer2 = customerMapper2.getCustomerWithId(2);
        System.out.println("customer2=="+customer2);
        System.out.println("是否相等:"+(customer2==customer));
        sqlSession2.close();
    }

运行结果
在这里插入图片描述

不使用动态代理的方式

 @Test
    public  void Test3(){
        SqlSession sqlSession = MybatisUtils.openSession();
        Customer customer = sqlSession.selectOne("getCustomerWithId", 2);
        System.out.println("customer=="+customer);
        sqlSession.close();

        SqlSession sqlSession2= MybatisUtils.openSession();
        Customer customer2 = sqlSession2.selectOne("getCustomerWithId", 2);
        
        System.out.println("customer2=="+customer2);
        System.out.println("是否相等:"+(customer2==customer));
        sqlSession2.close();
    }

运行结果
在这里插入图片描述

结果与上图一模一样
可知缓存与用何种方式执行SQL无关

但我们修改配置
在这里插入图片描述
再次运行上面运行过的代码;比如我们这里运行test2
运行结果如图:抛出该JavaBean不是可序列化的异常
在这里插入图片描述
于是我们在该类上实现Serializable接口
在这里插入图片描述
再次运行
在这里插入图片描述

运行成功,并且只执行了一次SQL语句,第二次查询缓存命中。但是查询出来的两个对象却不相等了。

我们再次回到cache的属性上面:
readOnly:是否只读 。
true:只读,mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。 mybatis为了加快获取速度,直接会将数据在缓存中的引用交给用户,不安全,但速度快。
false:非只读,mybatis觉得获取的数据可能会被修改 mybatis会利用序列化&反序列化的技术克隆一份新的数据给你,安全,但速度慢。

  因为上面说到mybatis会用到序列化&反序列化技术克隆一份新的数据,所以对应的pojo需要实现序列化。 并且既然是复制一份新的数据给你,所以两次查询的对象并不相等了 这也可以算是和一级缓存的差别之一了。

再次回顾cache相关属性

cache的相关属性介绍:
eviction: 缓存的回收策略
LRU(默认):最近最少使用,移除最长时间不被使用的对象
FIFO:先进先出,安对象进入缓存的顺序来移除它们
SOFT:软引用,移除基于垃圾回收器的状态和软引用规则的对象
WEAK: 弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象
flushInterval:缓存刷新间隔 缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读 。
true:只读,mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。 mybatis为了加快获取速度,直接会将数据在缓存中的引用交给用户,不安全,但速度快。
false:非只读,mybatis觉得获取的数据可能会被修改 mybatis会利用序列化&反序列化的技术克隆一份新的数据给你,安全,但速度慢。
size:缓存最多存放多少个引用。
type:指定自定义缓存的全类名,实现Mybatis提供的Cache接口即可。

总结

1.一级缓存的作用域是一个sqlsession内;二级缓存作用域是针对mapper进行缓存;
2.一级缓存是默认开启的;二级缓存需要手动配置(参见第6点)
3.一级缓存sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域。sqlSession.close()后一级缓存也没有了。但是销毁sqlSession后会将里面的缓存存到二级缓存中;
4.二级缓存cache中readonly属性如果为false 那么相应的pojo类必须实现Serializable接口 并且其缓存查询到的对象都是通过序列化或者反序列化克隆的,所以对象之间两两不相等
5.二级缓存的生命周期和应用同步,它是用来解决一级缓存不能跨会话共享数据的问题,范围是namespace级别的,可以被多个会话共享(只要是同一个接口的相同方法,都可以进行共享)。
6.只要没有显式地设置cacheEnabled为false,都会使用CachingExector装饰基本的执行器(SIMPLE、REUSE、BATCH)。 二级缓存总是默认开启的但是每个Mapper的二级开关是默认关闭的。
7.二级缓存进行增删改操作也会刷新二级缓存,导致二级缓存失效

代码测试如下:

   @Test
    public  void Test3(){
        SqlSession sqlSession = MybatisUtils.openSession();
        Customer customer = sqlSession.selectOne("getCustomerWithId", 2);
        System.out.println("customer=="+customer);
        sqlSession.close();

        SqlSession sqlSession2= MybatisUtils.openSession();
        Customer customer2 = sqlSession2.selectOne("getCustomerWithId", 2);
        System.out.println("customer2=="+customer2);
        sqlSession2.close();
        SqlSession sqlSession3= MybatisUtils.openSession();
        Customer customer3 = sqlSession3.selectOne("getCustomerWithId", 2);
        System.out.println("customer3=="+customer3);
        System.out.println("2、1是否相等:"+(customer2==customer));
        System.out.println("3、1是否相等:"+(customer3==customer));
        System.out.println("3、2是否相等:"+(customer3==customer2));
        sqlSession3.close();
    }

运行结果:
在这里插入图片描述

什么情况下有必要去开启二级缓存

1、因为所有的增删改都会刷新二级缓存,导致二级缓存失效,所以适合在查询为主的应用中使用,比如历史交易、历史订单的查询。否则缓存就失去了意义。
2、如果多个namespace中有针对于同一个表的操作,比如Blog表,如果在一个namespace中刷新了缓存,另一个namespace中没有刷新,就会出现读到脏数据的情况。所以,推荐在一个Mapper里面只操作单表的情况使用。

  • 10
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值