你不能不知道的Mybatis缓存机制!
同样地,mybatis作为ORM框架,也必然会支持缓存
它分别支持一级缓存和二级缓存。其中一级缓存是sqlSession级的缓存,而二级缓存则可以实现多个sqlSession间的缓存
什么意思?往下看喽~
一级缓存
01. 什么是一级缓存
之所以说mybatis的一级缓存是sqlSession级的,是因为它只支持同一个sqlSession下的缓存,也就是说缓存只在同一个sqlSession之间共享
02.如何实现一级缓存
一级缓存分为两个范围:statement和session
statement:说白了就是一个sql语句
session:有数据库产生的一个连接,即一个sqlsession
而mybatis默认支持一级缓存,不需要专门进行配置,并且它支持session范围的一级缓存。
针对缓存属性,mybatis通过类org.apache.ibatis.sessionConfiuration进行了配置,我们可以看到localCacheScope的默认级别为SESSION(并且二级缓存的也是默认开启的)
注意:Configuration类中的cacheEnabled属性是针对二级缓存的开关控制,而不是针对一级缓存的。一级缓存完全不需要进行配置,它并没有开关,是Mybatis默认支持的
那么,也就是说,我直接运行服务,一级缓存就生效了?
试试看,进入一级缓存测试环节~
03. 一级缓存测试
public void testCache(){
User user = userMapperWithAnnotation.findById(33L);
log.info("Find User: {}", user);
User user2 = userMapperWithAnnotation.findById(33L);
log.info("Find User2: {}", user2);
}
@SpringBootApplication
@Slf4j
@MapperScan("com.shumile.springbootmybatis.mapper")public class SpringBootMybatisApplication
implements ApplicationRunner {
@Autowired
private UserMapperWithAnnotation userMapperWithAnnotation;
public static void main(String[] args) {
SpringApplication.run(SpringBootMybatisApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
testCache();
}
哈?什么情况?骗人呢?打印了两条sql语句,这不还是查询了两次吗?
说实话,这个问题曾经困扰了我好几个小时,我在想,难道网上说的都有问题吗?是不是对于一级缓存,还专门有什么特殊配置呢?
最后呢,通过源码的跟踪,终于豁然开朗~
04. 源码分析
2)接着,会进入selectList()方法
我们知道在Confifuration类中已经默认配置了一级缓存的支持范围,在执行查询语句的时候,我们拿到的对应配置对象 就是默认支持session范围内的缓存(依旧先不用管cacheEnabled属性)。
那么,这块是没有问题的
3)往下走,进入CachingExecutor的query()方法
根据参数,获取到对应的可执行sql语句之后,进入创建缓存key的方法createCacheKey(),并将获取到的key作为参数传给下面的query()方法
在这里,先暂停,我们进入createCacheKey()方法内部,看看这个Key是如何确定的
4)createCacheKey()实现探究
它通过分别执行cacheKey.update()方法,将Statement.id、Offset、Limmit、Sql以及Params,这五个属性分别,放入cacheKey中的updateList中。其中update()方法如下
很显然,除去hashcode,checksum和count的比较外,只要updatelist中的元素对应相等了,就可以认为是CacheKey相等。也就是说,只要两条Sql的Statement.id、Offset、Limmit、Sql以及Params这五个值相同,即可以认为是相同的Sql
看完了对于key的组装环节,接着继续往下走~
5)在缓存中找key
往下执行,在query()方法中,会首先通过key值去缓存中取,如果缓存中没有,即获取到的list为为空,则需要去数据库中查询
6)执行具体的数据库查询方法
7)将结果存入缓存
执行以后,会通过localCache.putObject(key,list)将当前执行的结果放在本地缓存中
8)提交结果
这一步结束以后,便进入到了commit()方法
我们看到,在执行commit()方法时,会清空本地缓存。那么以后再次查询时,缓存中总是找不到对应的key值,就会出现每次都重新执行sql语句,去数据库中查询的现象了
那么,我们便很容易就知道了,为什么会不支持一级缓存了。
原来,要想支持一级缓存,就得要保证在这些sql语句全部执行完以后,再去执行commit()方法,也就是说,我们的方法必须要在同一个事务内,才会支持
那就一起来验证一下,开启了事务的情况~
1)开启事务
接下来,我们对方法开启事务,在启动类添加@EnableTransactionManagement注解,并在run()方法上添加@Transactional注解
2)验证环节
sql语句只执行了一次,那么说明验证成功~
小结
很显然,那是因为它每条语句执行结束以后,都会执行提交方法,而提交方法在每次都会清空本地缓存。而开启了事务的话,方法是在所有操作结束以后才会提交,因此就会支持一级缓存啦
二级缓存
01. 什么是二级缓存
一级缓存中,是一个sqlSession使用一个缓存,而mybatis的二级缓存,则支持多个SqlSession之间共享缓存。mybatis默认开启二级缓存
02. 如何二级缓存
cache标签用于声明namespace使用二级缓存,并且可以通过以下属性自定义配置
type
:cache使用的类型,默认是PerpetualCache
eviction
:定义回收的策略,常见的有FIFO,LRUflushInterval
:配置一定时间自动刷新缓存size
:最多缓存对象的个数readOnly
:是否只读,若配置可读写,则需要对应的实体类能够序列化blocking
:配置若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存
cache-ref
代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache
要想实现两个命名空间共享缓存,那么可以cache-ref标签的namespace属性引入另一个命名空间,如:
--标签说明参考自:
https://tech.meituan.com/2018/01/19/mybatis-cache.html
03. 二级缓存测试
要想验证mybatis的二级缓存,就需要构造多个不同sqlsession的操作,验证在这些sqlsession之间能够实现缓存共享
1)创建mybatis-configuration.xml文件
xml version="1.0" encoding="UTF-8"?>
configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
name="logImpl" value="STDOUT_LOGGING"/>
name="cacheEnabled" value="true" />
default="mysql">
id="mysql">
type="JDBC">
type="POOLED">
name="driver" value="com.mysql.jdbc.Driver">
name="url" value="jdbc:mysql://127.0.0.1:3306/test">
name="username" value="root">
name="password" value="123456">
resource="mapper/UserMapper.xml">
2)我们通过这个配置文件,分别定义两个sqlsession,进行同样的查询操作
我们看到,并没有实现缓存的共享,即二级缓存失效了
3)第二次测试
我们在执行第二个sqlsession的操作之前,先执行sqlSession1的提交
我们看到,二级缓存成功实现了
咦~这又是为什么呢?
这是因为呢,只有第一个sqlSession执行了提交操作,第二个sqlSession才能感应到,然后才能获取到这个缓存
那么,如果我们的数据发生了变化,它肯定就不能再去取缓存中的数据了,否则我们在页面中看到的数据就不是最新的了
这一点,mybatis也为我们考虑到了,我们来看看如果我们在操作中间,执行了更新表的操作,情况是怎样的
4)第三次测试
如下所示,在两个查询操作之间,我们执行了另一个sqlSession的update()方法
执行结果,如下所示
小结
mybatis通过cacheEnabled来进行二级缓存的开启与关闭配置,默认是开启状态
使用时,还需要在mapper.xml中通过cache标签开启命名空间内部的缓存。当然也可以通过cache-ref加入其它命名空间,进行二级缓存的共享
Mybatis对多表查询有局限性,容易出现读到脏数据。建议使用第三方缓存实现
三 总结