MyBatis缓存机制

MyBatis缓存机制

MyBatis定义了两级缓存:一级缓存、二级缓存
一级缓存:同一个SqlSession对象(与数据库的同一次会话期间),多次调用同一个方法的同一个参数,只会第一次去数据库查询,然后把数据写到一级缓冲中,以后直接先从缓存中取出数据,不会直接去查数据库。

二级缓存:与数据库第一次会话,数据就会缓存到一级缓存中,会话关闭后,一级缓存中的数据就会保存到二级缓存中。

  • Mybatis默认开启了一级缓存,一级缓存是SqlSession级别的缓存,也称本地缓存
  • Mybatis允许自定义二级缓存,通过Cache缓存接口实现
    Mybatis默认开启了全局二级缓存,要生效还需要在每个Mapper接口对应sql映射文件中进行配置,二级缓存是基于Mapper级别的缓存,也称全局缓存

缓存查询顺序

先查二级缓存,再查一级缓存,再查数据库;即使在一个sqlSession中,也会先查二级缓存。

一级缓存和二级缓存的作用域

一级缓存的作用域有两种:session(默认)和statment,可通过设置local-cache-scope 的值来切换,默认为session。
二者的区别在于session会将缓存作用于同一个sqlSesson,而statment仅针对一次查询,所以,local-cache-scope: statment可以理解为关闭一级缓存。

  • 一级缓存的作用域默认是与数据库的一次会话【一个SqlSession对象】
  • 二级缓存的作用域是同一个namespace下的mapper映射文件内容,即同一个Mapper接口下所有方法的SQL映射。
  • 个人理解:一个namespace命名空间对应一个二级缓存,即一个mapper接口对应一个二级缓存。同一个namespace命名空间下与数据库会话执行的sql语句,可以共享同一个二级缓存中的数据

一级缓存

一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION。
如果想关闭一级缓存,可以设置一级缓存的作用范围为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除

mybatis:
  configuration:
    local-cache-scope: SESSION

同一个SqlSession对象,同一个Mapper接口实现类的不同对象,可以共享一级缓存数据。

@Test
@Transactional
@Rollback(value = false)
public void test01() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    GoodsMapper mapper1 = sqlSession.getMapper(GoodsMapper.class);
    Goods goods1 = mapper1.getGoodsById(4151468384192018609L);
    System.out.println(goods1);
    
    GoodsMapper mapper2 = sqlSession.getMapper(GoodsMapper.class);
    Goods goods2 = mapper2.getGoodsById(4151468384192018609L);
    System.out.println(goods2);
}

运行结果:同一个mapper接口实现类的不同对象共享了一级缓存数据
在这里插入图片描述

一级缓存失效的五种情况

(1) SqlSession对象不同
不同的SqlSession对象之间缓存不能共享。

@Test
@Transactional
@Rollback(value = false)
public void test01(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    GoodsMapper mapper1 = sqlSession1.getMapper(GoodsMapper.class);
    Goods goods1 = mapper1.getGoodsById(1L);
    System.out.println(goods1);

    final SqlSession sqlSession2 = sqlSessionFactory.openSession();
    GoodsMapper mapper2 = sqlSession2.getMapper(GoodsMapper.class);
    Goods goods2 = mapper2.getGoodsById(1L);
    System.out.println(goods2);
}

(2) SqlSession对象相同,查询条件不同

@Test
@Transactional
@Rollback(value = false)
public void test01(){
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    GoodsMapper mapper1 = sqlSession1.getMapper(GoodsMapper.class);
    Goods goods1 = mapper1.getGoodsById(4151468384192018609L);
    System.out.println(goods1);
    Goods goods2 = mapper1.getGoodsById(4151468384192018610L);
    System.out.println(goods2);
}

(3)SqlSession对象相同,两次查询之间执行了增删改操作

@Test
@Transactional
@Rollback(value = false)
public void test01(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
    Goods goods = mapper.getGoodsById(4151468384192018609L);
    System.out.println(goods);
    Goods g = new Goods();
    g.setGoodsType(6);
    g.setGoodsName("test");
    g.setGoodsStatus(1);
    mapper.insert(g);
    System.out.println(g.getGoodsId());
    Goods goods1 = mapper.getGoodsById(4151468384192018609L);
    System.out.println(goods1);
}

(4)SqlSession对象相同,手动清除了一级缓存: sqlSession.clearCache()

@Test
@Transactional
@Rollback(value = false)
public void test01() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    GoodsMapper mapper = sqlSession.getMapper(GoodsMapper.class);
    Goods goods = mapper.getGoodsById(4151468384192018609L);
    sqlSession.clearCache(); //清除一级缓存
    Goods goods1 = mapper.getGoodsById(4151468384192018609L);
    System.out.println(goods1);
}

(5)使用数据库连接池时,未开启事务会导致一级缓存失效

使用数据库连接池导致一级缓存失效

当使用数据库连接池时,默认每次查询完之后会自动进行事务提交,这就导致两次查询使用的不是同一个sqlSessioin,一级缓存永远也不会生效。当我们在方法上加事务注解@Transactional,两次查询都在同一个sqlSession中,从而让第二次查询命中了一级缓存

二级缓存

SpringBoot启用二级缓存

二级缓存是默认启用的,但要生效还需要对每个Mapper接口的映射文件进行配置。

  • 在需要启用二级缓存的Mapper接口对应的Mapper.xml中定义的cache,并且实体对象必须实现序列化接口
<mapper namespace="top.onething.ssm.mapper.GoodsMapper">
    <cache/>
    <select id="getGoodsById" resultType="top.onething.ssm.entity.Goods">
        select * from cmm_goods where goods_id=#{goodsId}  limit 
    </select>
    <insert id="insert" useGeneratedKeys="true" keyColumn="goods_id" keyProperty="goodsId">
        insert into cmm_goods(goods_type,goods_name,goods_status)
        value(#{goodsType},#{goodsName},#{goodsStatus})
    </insert>
</mapper>

二级缓存是默认启用的,如果想显示的启用,则需要在配置文件中配置mybatis.configuration.cache-enabled=true,此外也可以通Spring的方法启用,参考下文中spring方式禁用二级缓存

@Test
@Transactional
@Rollback(value = false)
public void test01() {
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    GoodsMapper mapper1 = sqlSession1.getMapper(GoodsMapper.class);
    Goods goods1 = mapper1.getGoodsById(4151468384192018609L);
    System.out.println(goods1);
    //关闭会话后,一级缓存中数据才会保存到二级保存中,同时一级缓存清空
    sqlSession1.close();
    
	SqlSession sqlSession2 = sqlSessionFactory.openSession();
	GoodsMapper mapper2 = sqlSession2.getMapper(GoodsMapper.class);
	Goods goods2 = mapper2.getGoodsById(4151468384192018609L);
	System.out.println(goods2);
}

运行结果:
在这里插入图片描述
Cache Hit Ratio 表示缓存命中率。开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。图中第一次查询也是先从缓存中查询,只不过缓存中一定是没有的,所以命中率为0。但第二次查询是从二级缓存中读取的,所以这一次的命中率为1/2=0.5。当然,若有第三次查询,则命中率为2/3=0.66

SpringBoot禁用二级缓存

  • 方式1:
mybatis:
  configuration:
    cache-enabled: false
  • 方式2:采用Spring的方式配置

第一步:通过application.yml指定Mybatis配置文件的位置

mybatis:
  config-location: classpath:mybatis-config.xml   

第二步:在指定的mybatis配置文件mybatis-config.xml中配置二级缓存

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="cacheEnabled" value="false"/>
    </settings>
</configuration>

通过useCache属性指定Mapper接口中的方法禁用二级缓存

<select id="getGoodsById" resultType="top.onething.ssm.entity.Goods" useCache="false">
  select * from cmm_goods where goods_id=#{goodsId}
</select>

多个Mapper接口共用一个二级缓存

假设MenuMapper.xml想共用UserMapper.xml定义的namespace,即共用二级缓存,则在MenuMapper.xml的cache-ref应该如下定义:

<cache-ref namespace="top.onething.mapper.UserMapper"/>

二级缓存的使用原则

  • 只能在一个命名空间下使用二级缓存
    由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。
  • 在单表上使用二级缓存
    如果一个表与其它表有关联关系,那么久非常有可能存在多个namespace对同一数据的操作。而不同namespace中的数据互补干扰,所以就有可能出现多个namespace中的数据不一致现象。
  • 查询多于修改时使用二级缓存
    在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将清空二级缓存,对二级缓存的频繁刷新将降低系统性能

使用mybatis二级缓存的缺陷:(一般情况下不使用mybatis的二级缓存,使用redis等替代)

  • 如果应用是分布式部署,由于二级缓存存储在本地,必然导致查询出脏数据,所以,分布式部署的应用不建议开启。
  • 多表联合查询的情况下极大可能会出现脏数据;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值