文章目录
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等替代)
- 如果应用是分布式部署,由于二级缓存存储在本地,必然导致查询出脏数据,所以,分布式部署的应用不建议开启。
- 多表联合查询的情况下极大可能会出现脏数据;