MyBatis缓存机制

    科技2022-07-11  98

    文章目录

    MyBatis缓存机制缓存查询顺序一级缓存和二级缓存的作用域一级缓存一级缓存失效的五种情况使用数据库连接池导致一级缓存失效 二级缓存SpringBoot启用二级缓存SpringBoot禁用二级缓存通过useCache属性指定Mapper接口中的方法禁用二级缓存多个Mapper接口共用一个二级缓存二级缓存的使用原则 使用mybatis二级缓存的缺陷:(一般情况下不使用mybatis的二级缓存,使用redis等替代)

    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等替代)

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