点一下关注吧!!!非常感谢!!持续更新!!!
大数据篇正在更新!
- MyBatis(正在更新)
一级缓存
MyBatis 是一个优秀的持久层框架,它通过提供 SQL 映射和对象关系映射功能简化了数据库操作。其中,MyBatis 的缓存机制是其重要的性能优化功能之一,分为一级缓存和二级缓存。一级缓存是默认开启的,且对开发者透明。
一级缓存是 MyBatis 中的本地缓存,作用范围是 SqlSession。在同一个 SqlSession 中执行的多次相同的查询操作,如果参数和 SQL 语句相同,MyBatis 会从缓存中直接返回查询结果,而不会重复访问数据库,从而提高了性能。
编写代码
一个 SqlSession中,对 User 表根据 ID 进行两次查询,查看 SQL 语句的情况。
public class WzkicuCache01 {
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<WzkUser> wzkUser = userMapper.findAll();
System.out.println(wzkUser);
List<WzkUser> wzkUser2 = userMapper.findAll();
System.out.println(wzkUser2);
sqlSession.close();
}
}
运行结果
运行之后,控制台输出结果如下:
24/11/13 10:12:08 DEBUG UserMapper.findAll: ==> Preparing: select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
24/11/13 10:12:08 DEBUG UserMapper.findAll: ==> Parameters:
24/11/13 10:12:08 DEBUG UserMapper.findAll: <== Total: 3
[WzkUser(id=1, username=wzk, pass..
对应的截图如下所示:
我们可以观察到,两次查询中间是没有执行的 SQL,这就是 MyBatis 的缓存机制。
编写代码
我这里为了测试方便,编写了一个 UPDATE 的方法,这里就不放代码了,大家自行实现就好。
这次我们在两次查询中间,做了一个 UPDATE 的操作。
public class WzkicuCache02 {
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询
List<WzkUser> wzkUser = userMapper.findAll();
System.out.println(wzkUser);
// 加入一次 UPDATE
WzkUser wzkUserUpdate = WzkUser
.builder()
.id(1)
.username("wzk-update")
.password("123-update")
.build();
wzkUserUpdate.setId(1);
userMapper.updateById(wzkUserUpdate);
sqlSession.commit();
// 再次查询
List<WzkUser> wzkUser2 = userMapper.findAll();
System.out.println(wzkUser2);
sqlSession.close();
}
}
运行结果
运行上述的代码,控制台的输出内容如下所示:
24/11/13 10:19:15 DEBUG UserMapper.findAll: ==> Preparing: select *,o.id oid from wzk_user u left join wzk_orders o on u.id=o.uid;
24/11/13 10:19:15 DEBUG UserMapper.findAll: ==> Parameters:
24/11/13 10:19:15 DEBUG UserMapper.findAll: <== Total: 3
[WzkUser(id=1, usern
对应的截图如下所示:
可以观察到,这次的运行结果,是:SQL 查询、更新操作、SQL 查询,和之前不同,这就是因为 UPDATE 之后,MyBatis 会清空缓存。
暂时小结
第一次发起查询的时候,先去缓存中查询是否命中,如果没有,从数据库中查询数据出来,将对应的数据保存到一级缓存中。
如果中间做了 SqlSession 的 Commit 操作,此时则会清空 SqlSession 的一级缓存,为了中缓存中拿到最新的消息,避免脏读。
第二次发起查询用户信息的时候,先去缓存中查询是否有对应的缓存,如果有直接取。
工作原理
一级缓存存储在 SqlSession 的内部,它基于 Java 的 HashMap 结构实现,使用查询的 SQL 语句和参数作为键,查询结果作为值。
工作流程
查询前检查缓存
SqlSession 会先检查缓存中是否已经存在相应的数据(即是否有相同 SQL 和参数的查询结果)。
如果缓存命中,则直接返回缓存中的结果,不再执行 SQL 语句。
查询后更新缓存
如果缓存未命中,MyBatis 会执行 SQL 查询,获取结果,并将结果存储在一级缓存中。
缓存的失效
在某些情况下,一级缓存会失效(见下文)。
一级缓存的特点
- 作用范围: 仅限于当前的 SqlSession。
- 线程安全: SqlSession 是线程不安全的,因此一级缓存仅在单个线程中有效,保证了缓存的正确性。
- 生命周期: 一级缓存的生命周期与 SqlSession 一致,当 SqlSession 被关闭或销毁时,缓存会被清空。
- 缓存粒度: 一级缓存的粒度较细,限定在单个 SqlSession。
一级缓存的生效条件
为了确保缓存的正确性,一级缓存有一定的生效条件,只有在以下情况下缓存才会命中:
- 相同的 SQL 语句:查询的 SQL 和参数必须完全相同。
- 相同的 SqlSession:必须在同一个 SqlSession 实例中进行查询操作。
- 没有执行过更新操作:在执行 INSERT、UPDATE 或 DELETE 操作后,一级缓存会被清空。
- 未显式清空缓存:如果手动调用了 clearCache() 方法,一级缓存会被清空。
一级缓存的失效场景
一级缓存会在以下情况下失效:
- 不同的 SqlSession:如果查询发生在不同的 SqlSession 中,一级缓存会失效,因为每个 SqlSession 都有独立的缓存。
- 执行更新操作:当执行了 INSERT、UPDATE 或 DELETE 语句后,MyBatis 会默认清空当前 SqlSession 的一级缓存,以保证数据的一致性。
- 显式清空缓存:如果在代码中调用了 SqlSession 的 clearCache() 方法,会清空一级缓存。
- 查询条件发生变化:如果查询的 SQL 或参数发生变化(即使是查询相同的表),一级缓存不会命中。
原理探究
一级缓存到底是什么?一级缓存什么时候被创建?一级缓存的工作流程是什么样子的?
我们可以一级一级的跟进 SqlSession 中进行查看。
从前面我们手动实现 MyBatis 的过程中可以看出来,比如 SqlSession是个接口,那肯定有一个对应的实现类(大概率会叫:DefaultSqlSession),比如 Executor,那肯定也会有一个对应的实现类(大概率叫 DefaultExecutor),我们一级一级跟入代码。
我们将会在:BaseExecutor.java 中查看到如下的代码:
(略过整个查看源码的过程,可以自行查看,大概有个概念即可)。
在上述的所有方法中,clearCache 是和缓存相关的,我们要对上下级关系有一个清晰的了解。
再深入分析,流程走到 Perpetualcache 中的 clear 方法之后,会调用其他的 clear 方法,会看到一个 cache,这个 cache 是什么呢,本质上就是 new HashMap()。
也就是说,cache.clear() 实际上是 map.clear(),也就是说 SqlSession 中是有一个 HashMap 的。
Executor
我们跟入到 Executor 中,看几个方法,比如 commit 的时候,会调用 clearLocalCache 的方法。
在上述定义的地方,有对 localCache 的定义:
而 PerpetualCache 本质上就是一个大的 Map<Object, Object>
而创建缓存的地方在这里:
创建 Key 会经过一系列的 UPDATE 方法,UPDATE 方法由一个 CacheKey 对象来执行,这个 UPDATE 方法最终由 updateList 把这 5 个值都存起来。创建缓存结束之后,我们会在 query 的时候用到: