博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Mybatis一级缓存与二级缓存原理及失效场景
阅读量:2066 次
发布时间:2019-04-29

本文共 5729 字,大约阅读时间需要 19 分钟。

目录

1、前言

mybatis绝对是目前绝大部分Java程序员日常开发必不可少的框架了,然而尴尬的是,大部分人都处于API工程师阶段,甚至连最基本的一级缓存和二级缓存是啥都不知道。

今天,通过这篇博客来带大家扫扫盲,了解一下mybatis中的一级缓存和二级缓存~

2、一级缓存

假设我们现在有这么一个场景:需要从10w条数据中拿到用户id,然后再去用户表把用户信息查询出来。

需求实现起来很简单:分页获取数据,遍历数据根据用户id查询用户信息

但是你有没有想过,如果这10w条数据都是一个用户操作的呢?在这种极端情况下,程序会进行10w次重复操作,为了避免这种情况,mybatis引入了一级缓存。

@Testpublic void test1() throws IOException {
InputStream fileInputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(fileInputStream); SqlSession sqlSession = factory.openSession(true); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.selectByid(1); User user2 = userMapper.selectByid(1); System.out.println(user1.equals(user2));}

在这里插入图片描述

在该示例代码中,控制台只打印了一次查询语句。

2.1 会话缓存

在同一次查询会话中如果出现相同的语句及参数,就会从缓存中取出不再走数据库查询。

一级缓存只能作用于查询会话中,所以也叫做会话缓存。

那么问题来了,什么是会话呢?在WEB系统中最直白的意思就是:一次请求期间。

而在mybatis中的表现形式为:

SqlSession sqlSession = factory.openSession(true);// ……sqlSession.close();

从open到close之间的都是同一个会话。

2.2 一级缓存什么时候失效

既然是缓存,失效是必然存在的。那么在mybatis中,一级缓存在什么场景下会失效呢?

  1. 必须是相同的会话
  2. 必须是同一个mapper 接口中的同一个方法
  3. 中间没有执行session.clearCache() 方法
  4. 查询语句中间没有执行insert、update、delete方法

第一点在刚才已经说过了,既然是会话缓存,肯定必须是相同的会话。第二点也很好理解,方法都不一样了,你们要抓周树人,和我鲁迅有什么关系~

第三点就不用说了,缓存都清空了不失效还等着过年啊?

但是这里需要说明一下第四点,我们对刚才的代码进行一点点改动:

public void test1() throws IOException {
InputStream fileInputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(fileInputStream); SqlSession sqlSession = factory.openSession(true); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.selectByid(1); userMapper.insertUser(new User()); User user2 = userMapper.selectByid(1); System.out.println(user1.equals(user2));}

请问这种情况下还能命中缓存吗?既然刚才已经摆出结论了,肯定是无法命中的。

在这里插入图片描述

那如果是orderMapper执行了insert操作,结果会怎么样呢?答案是仍然无法命中,不是说好的抓周树人吗?怎么鲁迅也没了?

2.3 一级缓存的真相

要回答这个问题,我们得了解一下这个一级缓存到底长啥样,以及一级缓存是什么时候存入和清空的。

通过debug我们可以得到如下调用链:

在query方法中,我们能看到如下代码:

list = resultHandler == null ? (List)this.localCache.getObject(key) : null;if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}

从localCache中尝试获取数据,如果list为null,才调用queryFromDatabase方法。

这个localCache就是我们要找的一级缓存,让我们来看看它长啥样:

这是一个hashMap。

然后在queryFromDatabase方法中可以看到,一级缓存是在这时候被放进去的:

那么一级缓存的清空又是在啥时候呢?

同样通过debug我们可以得到如下的调用链:

在update方法中调用了this.clearLocalCache(),很明显,这是一个清空缓存的方法,

public void clearLocalCache() {
if (!this.closed) {
this.localCache.clear(); this.localOutputParameterCache.clear(); } }

注意,这里是直接把一级缓存里面所有数据都清空了,也就是说,管你是鲁迅还是周树人还是张三李四,统统一股脑全抓起来。

2.4 一级缓存是不安全的

那么问题来了,刚才说的清空,指的是当前会话的缓存呢还是所有会话中的缓存呢?

如果是当前会话中的缓存,那岂不是会有并发问题?

在查询时另一个会话并发去修改查询的数据,是否就会导致数据不正确?

我们来做这样一个操作

public static void main(String[] args) throws IOException {
InputStream fileInputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(fileInputStream); SqlSession sqlSession = factory.openSession(true); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); new Thread(new Runnable() {
@Override public void run() {
User user1 = userMapper.selectByid(1); System.out.println("线程一:"); System.out.println(user1); try {
Thread.sleep(2000); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("线程一:"); System.out.println(user1); } }).start(); try {
Thread.sleep(1000); } catch (InterruptedException e) {
e.printStackTrace(); } new Thread(new Runnable() {
@Override public void run() {
User user2 = userMapper.selectByid(1); user2.setName("周树人"); System.out.println("线程二:"); System.out.println(user2); sqlSession.close(); } }).start();}

在这里插入图片描述

线程一和线程二同用一个session,然后线程一查询出来的李四居然被线程二查询出来的对象影响了!好好的李四变成了周树人……

3、二级缓存

相较于一级缓存的简单与脆弱,二级缓存可就强大多了(同时也复杂了许多……)

在我们的业务中,很多数据其实存在读多写少情况的,也就是说这些数据在缓存中应该生存挺久才对。但是一级缓存并不能达到我们想要的目的,于是mybatis就设计了二级缓存。

要使用二级缓存,我们需要在mapper上添加@CacheNamespace注解,然后运行如下实例:

@Testpublic void test2() throws IOException {
InputStream fileInputStream = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(fileInputStream); SqlSession sqlSession = factory.openSession(true); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user1 = userMapper.selectByid(1); sqlSession.close(); SqlSession sqlSession1 = factory.openSession(true); UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); User user2 = userMapper1.selectByid(1); System.out.println(user1.equals(user2));}

在这里插入图片描述

可以看到,我们不仅命中了二级缓存(只查询了一次),同时两次查询出来的对象也不是同一个。

3.1 二级缓存使用条件

我们先来看看,什么情况下二级缓存才会生效:

  1. 当会话提交或关闭之后才会填充二级缓存
  2. 必须是在同一个命名空间之下
  3. 必须是相同的statement 即同一个mapper 接口中的同一个方法
  4. 必须是相同的SQL语句和参数
  5. 如果readWrite=true ,实体对像必须实现Serializable 接口

第2、3、4条很好理解,这里稍微说一下1和5。二级缓存之所以不会产生一级缓存的问题,就是靠1和5来保证的。

首先,如果我们把1的约束去除,那么线程A在命中缓存时,有可能命中的就是其它线程会话未完成的脏数据。而如果把5的约束去除,那么线程A和线程B拿到的就不是序列化的对象,而是和一级缓存一样,直接拿的引用。

3.2 二级缓存清除条件

与一级缓存不同的是,一级缓存的清除是一把梭,但是二级缓存只会梭自己namespace中的缓存。

同时,只有修改操作才会清空缓存,并且任何一种增删改操作都会清空整个namespace 中的缓存。

3.3 二级缓存长啥样

这个问题的答案可能会让你出乎意料,因为在mybatis中,对二级缓存疯狂使用了装饰者模式,为什么我要用"疯狂"呢?

看看它的实现类就知道了:
在这里插入图片描述
并且你点进去任何一个实现类,都会发现它的构造器参数是传入一个Cache类型的对象:

也就是说,你可以把它们所有实现类全部套娃到一个对象中~

此外,我们可以看到,二级缓存实际上也是一个hashMap

4、结尾

事实上,在mysql8.0之前,也是有类似的缓存概念,但是在8.0之后被剔除了,因为这个缓存带来的提升并不明显,反而需要花费性能去维护。

mybatis的一级缓存个人觉得甚至还不如mysql被剔除的缓存好用……

转载地址:http://xmfmf.baihongyu.com/

你可能感兴趣的文章
谈谈编程思想
查看>>
iOS MapKit导航及地理转码辅助类
查看>>
检测iOS的网络可用性并打开网络设置
查看>>
简单封装FMDB操作sqlite的模板
查看>>
iOS开发中Instruments的用法
查看>>
iOS常用宏定义
查看>>
被废弃的dispatch_get_current_queue
查看>>
什么是ActiveRecord
查看>>
有道词典for mac在Mac OS X 10.9不能取词
查看>>
关于“团队建设”的反思
查看>>
利用jekyll在github中搭建博客
查看>>
Windows7中IIS简单安装与配置(详细图解)
查看>>
linux基本命令
查看>>
BlockQueue 生产消费 不需要判断阻塞唤醒条件
查看>>
强引用 软引用 弱引用 虚引用
查看>>
数据类型 java转换
查看>>
"NetworkError: 400 Bad Request - http://172.16.47.117:8088/rhip/**/####t/approval?date=976
查看>>
mybatis 根据 数据库表 自动生成 实体
查看>>
win10将IE11兼容ie10
查看>>
checkbox设置字体颜色
查看>>