背景
在阅读MyBatis官方文档时,发现了上述图片中的字样。可以得知,MyBatis中默认存在两种缓存模式。
恰恰这两个缓存,在面试中是重灾区,因为很多人并没有接触过,或者说,自己触发了这个都不知道。
这一篇文章来从如何使用,到源码是如何加载配置并且查询有无判断缓存的角度,来说一下这个事情。
阅读环境
源码环境,github直接下载的main分支代码,版本号为 3.5.11-SNAPSHOT
github点击跳转Ide Jetbrain Idea 2021.2.1 社区版
Maven 3.8.2
JDK 17
配置文件
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://******/**"/>
<property name="username" value="****"/>
<property name="password" value="******"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="LogChartMapper.xml"/>
</mappers>
</configuration>
测试主方法
public class Main {
public static void main(String[] args) throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
LogChartMapper mapper = sqlSession.getMapper(LogChartMapper.class);
List<LogChart> logCharts = mapper.selectList();
System.out.println(JSONUtil.toJsonStr(logCharts));
}
}
mapper
public interface LogChartMapper {
@Select(value = "select * from log_chart")
List<LogChart> selectList();
}
mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.test.mapper.LogChartMapper">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>
以上配置信息,基本和上一篇中相同。只是在mapper.xml中,多加了一个cache标签。
阅读过程
MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。
默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
摘自:MyBatis官文文档
在上文中,缓存分为会话缓存和全局二级缓存,那么由此可以提出本文的第一个问题,MyBatis是如何让二级缓存应用于全局的。
MyBatis是如何让二级缓存应用于全局的。
需要搞清楚这个问题,我们直接定位到,解析Cache标签的那部分的源码。
该方法位于XMLMapperBuilder类中,看这个cacheElement方法,就是处理Cache标签的相关代码了。
这片源码的上面的那个部分,可以看到都是通过context.getAttribute方法,获取cache标签上的属性值,然后传入了useNewChache方法中,这里可以继续追下去。
看到这里,算是知道了一个关键的地方,把New出来的Cache对象放入了configuration中。
存放Cache这里可以知道是存放Configuration对象中,那么接着看下,SqlSessionFactory在获取一个SqlSession时的源码是怎么做的。
接下来追factory在创建SqlSession的源码。
追到最后,是上述的代码,在这里就可以得到答案了。
关于MyBatis是如何让二级缓存应用于全局的,一个小结论
因为Configuration这个类,会被最终返回并存储到SqlSessionFactory中,那么在SqlSessionFactory在获取SqlSession时,也就是new DefaultSqlSession的时候,SqlSessionFactory会把Configuration放入SqlSession中,那么我们的Cache对象是放入Configuration中的,所以这样MyBatis就实现了将二级缓存应用与全局。\
二级缓存在哪里被使用到的
探索这个问题,我们走到MyBatis的动态代理的那部分源码中,看一下他的invoke方法的实现。
这一行代码随着一层一层的升入,会进入CachingExecutor类中的query方法。
可以看到,在这个方法中,有一个CreateCacheKey方法,这里创建了一个缓存Key,传入了下方的query方法中,到达这里后,可以继续追一下。
这里的代码就是我们最后的代码块了,详细的解析下,通过ms获取cache,也就是二级缓存。这里获取到了一个二级缓存,进入了IF判断。随后就调用了flushCacheIfRequired,这里见名知意,也就是刷新缓存在必要的时候,其实就是我们mapper.xml中写的方法上面的
<select id="xxxx" flushCache="true"></select>,随后就去tcm中获取Object。如果查询为空,就调用另外一个Query方法,随后把这个Query方法返回的List对象,存入tcm中,如果在tcm拿到了数据就直接返回了。
那么到了这里,我们上面的小结论需要修改一下,因为加入了一个执行器的存在。
前面重复的就不说了,主要是在sqlsession调用query方法时的问题。回去调用CachingExecutor的query方法,那么调用这个方法需要传入MappedStatement,MappedStament是在创建Config对象时,解析mapper.xml的时候生成的,生成的时候把当时的cache传入了进去,也就是二级缓存,这里就可以通过cache对象来获取对应的结果了。
最后结论
对应缓存的结论在上面都已经总结过了,下面说下我看MyBatis源码的一些经验。
那我们这里最后总结一下,如果感兴趣的情况下可以自己阅读一下MyBatis的源码,在阅读Batis源码的时候需要注意几个点,加载Config.xml的时候,细节很多,就算粗略看一眼也行。为什么这么说呢,后续当你不知道这个类在什么地方创建过的时候,基本上都是在解析xml的时候创建的,这个时候再看一眼,可能印象更深刻一点。好了今天的一文就到这里。
我们下期再见。
都看到这了,点个赞再走吧宝~