Java MyBatis本地缓存原理详解
作者:??江粒???? 发布时间:2023-01-30 18:20:36
背景
出现了一次生产事故,事情是这样的,我们有一个项目,Java访问数据库的框架使用的是MyBatis。然后一个业务员在系统中查询了一个订单,发现这个订单是未支付的状态,于是业务员联系客户,让客户支付,客户支付完成后,业务员又去系统查询,结果还是未支付状态,刷新了页面也是一样,不过过了一会就好了。业务员把这个延迟问题,反馈给了我们。我就看代码,只是一个简单的select * from order where id = ?
语句调用。这个时候就想到了我们今天故事的主角,MyBatis的缓存机制。
发现问题
复现
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
UsersMapper mapper = build.openSession().getMapper(UsersMapper.class);
List<Users> users = mapper.selectAll();
System.out.println(JSONUtil.toJsonStr(users));
// 在这睡眠期间去使用update语句修改数据库信息。
Thread.sleep(1000 * 10);
List<Users> users1 = mapper.selectAll();
System.out.println(JSONUtil.toJsonStr(users1));
}
}
看到上面的问题,很自然的就想到之前面试时候背的八股文,MyBatis的一二级缓存。
解决问题
其实这个问题,对于实效性不强的项目的话,完全可以和业务说,网络延迟,等一等就可以了。 如果说实用性比较强的项目,可以选择禁用这个缓存。不过这样也会带来一个问题,也就是没有缓存后,很多查询,查询结果相同的SQL语句,原本只需要执行一遍就可以,这里会请求很多次。增高DB的IO负担。
探究缓存的原理
Sql查询部分深入
在上一篇Java MyBatis是如何执行一条SQL语句的讲MyBatis的文章中已经说到了,mapper会经过 * 去执行SqlSession的query方法。
阅读缓存部分源码,需要跟随查询方法往下追着看。
接着,我们这里可以看到,一个Switch有很多的case,很显而易见,我们会进入Select中,随后Select的代码块中,又有很多个If判断,因为我们的方法返回的是一个List,正好就命中了returnsMany方法。
最终调用SqlSession中的Select方法,到达这里接着往里追。
到达执行器处理SQL语句的这一块了,可见封装了很多层,再往下追。
初见缓存
看到这里,终于见到缓存相关字样了,这里去CreateCacheKey,看到这里我突然想起了,刚工作时候的一次面试,有个傻*面试官,问我说MyBatis的缓存Key是怎么生成,当时我真想给他两耳巴。继续抠下面的query方法,可以看到把上面生成的key作为参数传了下来。
追到这里就可以看到一个比较关键的代码了,从localCache中调用了GetObject方法
这里看到下面的方法,从这里可以证明,MyBatis默认就是用本地缓存,所以写代码的时候自然也要记得处理缓存不一致的问题。
告一段落
到这里这一篇文章就结束了。一般来说,作为Java工程师,在工作中还是经常用到MyBatis这个点的,如果你在面试中真的问到了这么一道缓存题。而那么巧你就知道,那么郎有情,妾有意,恭喜您,点亮涨薪1K的成就。希望大家在当前大环境这么不好的情况下,多多学习,多多面试,提高核心竞争力,等大环境回暖各个年入50万+。
番外篇-Myabtis创建CacheKey的算法。
回到这一行代码,可以看到这个方法返回的是一个CacheKey的JavaClass,先不急着看这个方法的具体实现,先去看下这个类的构成,和构造方法。
构造方法
这个CacheKey类一共有两个构造方法,可以看到的在有参构造方法中,调用了无参方法,随后调用了,updateAll方法,能在构造中被调用的一般都是比较重要的,一会来看一下这个方法的实现,先来看下无参构造中的几个变量。
hashcode、multiplier、cout、updateList,值得注意的是hashCode和multiplier两个成员变量,都给赋值了初始值。
这两个变量记忆一下,然后去看updateAll方法。
这个方法比较简单,遍历了objects入参,传入update方法,继续去追update方法。
看到这里可以看出这里大概是计算HashCode的一个地方。只是最后会把算过的值放入UpdateList中。
随后去看这个类的tostring方法。
这里也就是具体的方法了
根据冒号分割,hashCode:checkSum:updateList的每个元素
再回过头来看一下CreateCacheKey方法看下最后生成是什么
最后答案留给评论互动吧。
结束语
来源:https://juejin.cn/post/7116528909675397151


猜你喜欢
- 封面图下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享~Flutter应用程序既括代码也包括一些其他的资产
- 在很多仿真和游戏应用中都需要大规模地形,这样会使3D环境似乎“无限大”,增加用户的真实感,比如飞行模拟游戏。那么在Unity中如何实现大规模
- 场景描述单例模式对于我们来说一点也不模式,是一个常见的名称,单例模式在程序中的实际效果就是:确保一个程序中只有一个实例,并提供一个全局访问点
- 建立工程前需要导入POI包。POI相关jar包下载地址:http://poi.apache.org/download.html1.解析.xl
- 目录为什么要用Geometry数据做图标?怎么获取Geometry数据?如何使用Geometry数据相信大家在阅读WPF相关GitHub开源
- Lucene从今天开始,我们要开始介绍Lucene中索引构建的流程。因为索引构建的逻辑涉及到的东西非常多,如果从构建入口IndexWrite
- 相同:1、LinkedBlockingQueue和ArrayBlockingQueue都实现了BlockingQueue接口;2、Linke
- 目录一、前言二、正文2.1 注解2.1.1 注解1:@Target({ElementType.TYPE})2.1.2 注解2:@Retent
- 前言『 * 』其实源于设计模式中的代理模式,而代理模式就是使用代理对象完成用户请求,屏蔽用户对真实对象的访问。举个最简单的例子,比如我们想
- 为了重用Fragment UI 组件,在设计中你应该通过定义每一个fragemnt自己的layout和行为,让fragment的自包含和模块
- using System; using System.Collections.Generic; using System.Linq; usi
- 本文实例为大家分享了java实现文件上传下载的具体代码,供大家参考,具体内容如下一.上传1.前端:<form method="
- 本次内容主要介绍基于Ehcache 3.0来快速实现Spring Boot应用程序的数据缓存功能。在Spring Boot应用程序中,我们可
- 在一个比较坑的需求里,一段文字右上角需要追加一个圆形红点。最右侧有个金额,红点动态随着文字移动,然后各种摆布局,一下午坑死我了。后来果断放弃
- 正常maven依赖jar包的pom.xml写法如下:<!-- https://mvnrepository.com/artifact/o
- 一、C# Thread类的基本用法通过System.Threading.Thread类可以开始新的线程,并在线程堆栈中运行静态或实例方法。可
- dart 是一个面向对象的语言;面向对象有继承封装多态dart的所有东西都是对象,所有的对象都是继承与object类一个类通常是由属性和方法
- Java基本概念JDK包含了不少Java开发相关命令。如,javac、java、javap、javaw、javadoc。虽然现在的Java开
- 主要是因为GZipStream的构造函数中第一个需要传入一个Stream,第二个是指定操作方式:压缩还是解压缩。当时的疑问点主要有:1.我传
- 本文实例讲述了Android实现内存中数据保存到sdcard的方法。分享给大家供大家参考,具体如下:public static void w