范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

给我五分钟,带你彻底掌握MyBatis的缓存工作原理

  前言
  在计算机的世界中,缓存无处不在,操作系统有操作系统的缓存,数据库也会有数据库的缓存,各种中间件如Redis也是用来充当缓存的作用,编程语言中又可以利用内存来作为缓存。自然的,作为一款优秀的ORM框架,MyBatis中又岂能少得了缓存,那么本文的目的就是带领大家一起探究一下MyBatis的缓存是如何实现的。给我五分钟,带你彻底掌握MyBatis的缓存工作原理 为什么要缓存
  在计算机的世界中,CPU的处理速度可谓是一马当先,远远甩开了其他操作,尤其是I/O操作,除了那种CPU密集型的系统,其余大部分的业务系统性能瓶颈最后或多或少都会出现在I/O操作上,所以为了减少磁盘的I/O次数,那么缓存是必不可少的,通过缓存的使用我们可以大大减少I/O操作次数,从而在一定程度上弥补了I/O操作和CPU处理速度之间的鸿沟。而在我们ORM框架中引入缓存的目的就是为了减少读取数据库的次数,从而提升查询的效率。 MyBatis缓存
  MyBatis中的缓存相关类都在cache包下面,而且定义了一个顶级接口Cache,默认只有一个实现类PerpetualCache,PerpetualCache中是内部维护了一个HashMap来实现缓存。
  下图就是MyBatis中缓存相关类:
  需要注意的是decorators包下面的所有类也实现了Cache接口,那么为什么我还是要说Cache只有一个实现类呢?其实看名字就知道了,这个包里面全部是装饰器,也就是说这其实是装饰器模式的一种实现。
  我们随意打开一个装饰器:
  可以看到,最终都是调用了delegate来实现,只是将部分功能做了增强, 其本身都需要依赖Cache的唯一实现类PerpetualCache(因为装饰器内需要传入Cache对象,故而只能传入PerpetualCache对象,因为接口是无法直接new出来传进去的) 。
  在MyBatis中存在两种缓存,即 一级缓存 和 二级缓存 。 一级缓存
  一级缓存也叫本地缓存,在MyBatis中,一级缓存是在会话(SqlSession)层面实现的,这就说明一级缓存作用范围只能在同一个SqlSession中,跨SqlSession是无效的。
  MyBatis中一级缓存是默认开启的,不需要任何配置。
  我们先来看一个例子验证一下一级缓存是不是真的存在,作用范围又是不是真的只是对同一个SqlSession有效。 一级缓存真的存在吗 package com.lonelyWolf.mybatis;  import com.lonelyWolf.mybatis.mapper.UserAddressMapper; import com.lonelyWolf.mybatis.mapper.UserMapper; import com.lonelyWolf.mybatis.model.LwUser; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;  import java.io.IOException; import java.io.InputStream; import java.util.List;  public class TestMyBatisCache {     public static void main(String[] args) throws IOException {         String resource = "mybatis-config.xml";         //读取mybatis-config配置文件         InputStream inputStream = Resources.getResourceAsStream(resource);         //创建SqlSessionFactory对象         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);         //创建SqlSession对象         SqlSession session = sqlSessionFactory.openSession();          UserMapper userMapper = session.getMapper(UserMapper.class);         List userList =  userMapper.selectUserAndJob();         List userList2 =  userMapper.selectUserAndJob();     } }
  执行后,输出结果如下:
  我们可以看到,sql语句只打印了一次,这就说明第2次用到了缓存,这也足以证明一级缓存确实是存在的而且默认就是是开启的。 一级缓存作用范围
  现在我们再来验证一下一级缓存是否真的只对同一个SqlSession有效,我们对上面的示例代码进行如下改变:  SqlSession session1 = sqlSessionFactory.openSession();  SqlSession session2 = sqlSessionFactory.openSession();   UserMapper userMapper1 = session1.getMapper(UserMapper.class);  UserMapper userMapper2 = session2.getMapper(UserMapper.class);  List userList =  userMapper1.selectUserAndJob();  List userList2 =  userMapper2.selectUserAndJob();
  这时候再次运行,输出结果如下:
  可以看到,打印了2次,没有用到缓存,也就是不同SqlSession中不能共享一级缓存。 一级缓存原理分析
  首先让我们来想一想,既然一级缓存的作用域只对同一个SqlSession有效,那么一级缓存应该存储在哪里比较合适是呢?
  是的,自然是存储在SqlSession内是最合适的,那我们来看看SqlSession的唯一实现类DefaultSqlSession:
  DefaultSqlSession中只有5个成员属性,后面3个不用说,肯定不可能用来存储缓存,然后Configuration又是一个全局的配置文件,也不合适存储一级缓存,这么看来就只有Executor比较合适了,因为我们知道,SqlSession只提供对外接口,实际执行sql的就是Executor。
  既然这样,那我们就进去看看Executor的实现类BaseExecutor:
  看到果然有一个localCache。而上面我们有提到PerpetualCache内缓存是用一个HashMap来存储缓存的,那么接下来大家肯定就有以下问题: 缓存是什么时候创建的?缓存的key是怎么定义的?缓存在何时使用缓存在什么时候会失效?
  接下来就让我们逐一分析 一级缓存CacheKey的构成
  既然缓存那么肯定是针对的查询语句,一级缓存的创建就是在BaseExecutor中的query方法内创建的:
  createCacheKey这个方法的代码就不贴了,在这里我总结了一下CacheKey的组成,CacheKey主要是由以下6部分组成 1、将Statement中的id添加到CacheKey对象中的updateList属性2、将offset(分页偏移量)添加到CacheKey对象中的updateList属性(如果没有分页则默认0)3、将limit(每页显示的条数)添加到CacheKey对象中的updateList属性(如果没有分页则默认Integer.MAX_VALUE)4、将sql语句(包括占位符?)添加到CacheKey对象中的updateList属性5、循环用户传入的参数,并将每个参数添加到CacheKey对象中的updateList属性6、如果有配置Environment,则将Environment中的id添加到CacheKey对象中的updateList属性一级缓存的使用
  创建完CacheKey之后,我们继续进入query方法:
  可以看到,在查询之前就会去localCache中根据CacheKey对象来获取缓存,获取不到才会调用后面的queryFromDatabase方法 一级缓存的创建
  queryFromDatabase方法中会将查询得到的结果存储到localCache中
  一级缓存什么时候会被清除
  一级缓存的清除主要有以下两个地方: 1、就是获取缓存之前会先进行判断用户是否配置了flushCache=true属性(参考一级缓存的创建代码截图),如果配置了则会清除一级缓存。2、MyBatis全局配置属性localCacheScope配置为Statement时,那么完成一次查询就会清除缓存。3、在执行commit,rollback,update方法时会清空一级缓存。
  PS:利用插件我们也可以自己去将缓存清除,后面我们会介绍插件相关知识。 二级缓存
  一级缓存因为只能在同一个SqlSession中共享,所以会存在一个问题,在分布式或者多线程的环境下,不同会话之间对于相同的数据可能会产生不同的结果,因为 跨会 话修改了数据是不能互相感知的,所以就有可能存在脏数据的问题,正因为一级缓存存在这种不足,所以我们需要一种作用域更大的缓存,这就是二级缓存。 二级缓存的作用范围
  一级缓存作用域是SqlSession级别,所以它存储的SqlSession中的BaseExecutor之中,但是二级缓存目的就是要实现作用范围更广,那肯定是要实现跨会话共享的, 在MyBatis中二级缓存的作用域是namespace,也就是作用范围是同一个命名空间 ,所以很显然二级缓存是需要存储在SqlSession之外的,那么二级缓存应该存储在哪里合适呢?
  在MyBatis中为了实现二级缓存,专门用了一个装饰器来维护,这就是我们上一篇文章介绍Executor时还留下的没有介绍的一个对象:CachingExecutor。 如何开启二级缓存
  二级缓存相关的配置有三个地方:
  1、mybatis-config中有一个全局配置属性,这个不配置也行,因为默认就是true。 
  想详细了解mybatis-config的可以点击这里。
  2、在Mapper映射文件内需要配置缓存标签: 
  想详细了解Mapper映射的所有标签属性配置可以点击这里。
  3、在select查询语句标签上配置useCache属性,如下: 
  以上配置第1点是默认开启的,也就是说我们只要配置第2点就可以打开二级缓存了,而第3点是当我们需要针对某一条语句来配置二级缓存时候则可以使用。
  不过开启二级缓存的时候有两点需要注意:
  1、需要commit事务之后才会生效
  2、如果使用的是默认缓存,那么结果集对象需要实现序列化接口(Serializable)
  如果不实现序列化接口则会报如下错误:
  接下来我们通过一个例子来验证一下二级缓存的存在,还是用上面一级缓存的例子进行如下改造:  SqlSession session1 = sqlSessionFactory.openSession();         UserMapper userMapper1 = session1.getMapper(UserMapper.class);         List userList =  userMapper1.selectUserAndJob();         session1.commit();//注意这里需要commit,否则缓存不会生效          SqlSession session2 = sqlSessionFactory.openSession();         UserMapper userMapper2 = session2.getMapper(UserMapper.class);         List userList2 =  userMapper2.selectUserAndJob();
  然后UserMapper.xml映射文件中,新增如下配置: 
  运行代码,输出如下结果:
  上面输出结果中只输出了一次sql,说明用到了缓存,而因为我们是跨会话的,所以肯定就是二级缓存生效了。 二级缓存原理分析
  上面我们提到二级缓存是通过CachingExecutor对象来实现的,那么就让我们先来看看这个对象:
  我们看到CachingExecutor中只有2个属性,第1个属性不用说了,因为CachingExecutor本身就是Executor的包装器,所以属性TransactionalCacheManager肯定就是用来管理二级缓存的,我们再进去看看TransactionalCacheManager对象是如何管理缓存的:
  TransactionalCacheManager内部非常简单,也是维护了一个HashMap来存储缓存。
  HashMap中的value是一个TransactionalCache对象,继承了Cache。
  注意上面有一个属性是临时存储二级缓存的,为什么要有这个属性,我们下面会解释。 二级缓存的创建和使用
  我们在读取mybatis-config全局配置文件的时候会根据我们配置的Executor类型来创建对应的三种Executor中的一种,然后如果我们开启了二级缓存之后,只要开启(全局配置文件中配置为true)就会使用CachingExecutor来对我们的三种基本Executor进行包装,即使Mapper.xml映射文件没有开启也会进行包装。
  接下来我们看看CachingExecutor中的query方法:
  上面方法大致经过如下流程: 1、创建一级缓存的CacheKey2、获取二级缓存3、如果没有获取到二级缓存则执行被包装的Executor对象中的query方法,此时会走一级缓存中的流程。4、查询到结果之后将结果进行缓存。
  需要注意的是 在事务提交之前,并不会真正存储到二级缓存,而是先存储到一个临时属性,等事务提交之后才会真正存储到二级缓存 。这么做的目的就是防止脏读。因为假如你在一个事务中修改了数据,然后去查询,这时候直接缓存了,那么假如事务回滚了呢?所以这里会先临时存储一下。
  所以我们看一下commit方法:
  二级缓存如何进行包装
  最开始我们提到了一些缓存的包装类,这些都到底有什么用呢?
  在回答这个问题之前,我们先断点一下看看获取到的二级缓存长啥样:
  从上面可以看到,经过了层层包装,从内到外一次经过如下包装: 1、PerpetualCache:第一层缓存,这个是缓存的唯一实现类,肯定需要。2、LruCache:二级缓存淘汰机制之一。因为我们配置的默认机制,而默认就是LRU算法淘汰机制。淘汰机制总共有4中,我们可以自己进行手动配置。3、SerializedCache:序列化缓存。这就是为什么开启了默认二级缓存我们的结果集对象需要实现序列化接口。4、LoggingCache:日志缓存。5、SynchronizedCache:同步缓存机制。这个是为了保证多线程机制下的线程安全性。
  下面就是MyBatis中所有缓存的包装汇总:
  二级缓存应该开启吗
  既然一级缓存默认是开启的,而二级缓存是需要我们手动开启的,那么我们什么时候应该开启二级缓存呢?
  1、因为所有的update操作(insert,delete,uptede)都会触发缓存的刷新,从而导致二级缓存失效,所以二级缓存适合在读多写少的场景中开启。
  2、因为二级缓存针对的是同一个namespace,所以建议是在单表操作的Mapper中使用,或者是在相关表的Mapper文件中共享同一个缓存。 自定义缓存
  一级缓存可能存在脏读情况,那么二级缓存是否也可能存在呢?
  是的,默认的二级缓存毕竟也是存储在本地缓存,所以对于微服务下是可能出现脏读的情况的,所以这时候我们可能会需要自定义缓存,比如利用redis来存储缓存,而不是存储在本地内存当中。 MyBatis官方提供的第三方缓存
  MyBatis官方也提供了一些第三方缓存的支持,如:encache和redis。下面我们以redis为例来演示一下:
  引入pom文件:              org.mybatis.caches             mybatis-redis             1.0.0-beta2         
  然后缓存配置如下: 
  然后在默认的resource路径下新建一个redis.properties文件: host=localhost port=6379
  然后执行上面的示例,查看Cache,已经被Redis包装:
  自己实现二级缓存
  如果要实现一个自己的缓存的话,那么我们只需要新建一个类实现Cache接口就好了,然后重写其中的方法,如下: package com.lonelyWolf.mybatis.cache;  import org.apache.ibatis.cache.Cache;  public class MyCache implements Cache {     @Override     public String getId() {         return null;     }     @Override     public void putObject(Object o, Object o1) {              }     @Override     public Object getObject(Object o) {         return null;     }      @Override     public Object removeObject(Object o) {         return null;     }      @Override     public void clear() {     }      @Override     public int getSize() {         return 0;     } }
  上面自定义的缓存中,我们只需要在对应方法,如putObject方法,我们把缓存存到我们想存的地方就行了,方法全部重写之后,然后配置的时候type配上我们自己的类就可以实现了,在这里我们就不做演示了 总结
  本文主要分析了MyBatis的缓存是如何实现的,并且分别演示了一级缓存和二级缓存,并分析了一级缓存和二级缓存所存在的问题,最后也介绍了如何使用第三方缓存和如何自定义我们自己的缓存,通过本文,我想大家应该可以彻底掌握MyBatis的缓存工作原理了。

2路4寸低音单元,来自枫叶国的桌面格调好物PSBAM3书架音箱作为一个不算太发烧的音乐爱好者,我对于桌面音箱一直都是颜值优先考虑,音质放在其次,当然也不能太拉胯,综合下来,能入耳看上眼的寥寥无几,如果再加上预算限制,掰着手指头数数也就那么三四有了新笔记本电脑,旧的苹果macbook又该怎么解决呢?众所周知,笔记本电脑根据系统不同大致可以分为Mac阵营和Windows阵营,随着苹果手机的热销,近年来,苹果macbook受到了更多消费者的追捧与喜爱,曾经远落后于Windows系为什么全球都嫌弃的iPhoneSE3在日本却能卖爆稿源中关村在线iPhoneSE3作为目前在售最便宜的搭载A15处理器的iOS设备,在发售之初备受关注,但上市之后与发售前火热的关注度形成鲜明的对比,由于落后时代的配置和过高的定价(中兴推出AX3000巡天版路由器自研双核主芯片5月9日,中兴举行Axon40系列手机发布会。除了Axon40系列以外,中兴还推出了全新的AX3000巡天版路由器。中兴AX3000巡天版路由器采用41五天线设计,可以覆盖1001值得推荐的几款蓝牙耳机在喧闹的城市中,奔波了一天的打工人都希望周围安静些,能够好好地休息一下享受一下音乐。所以现在很多用户在选择蓝牙耳机时会优先选择降噪效果比较好的,但是很多人其实不知道如何挑选降噪功能鸿蒙新机亮相,5000mAh100W,还有台积电4nm强芯坐镇自从受打压后,华为的手机业务严重影响了其销量。在四轮制裁下,现在华为手机因为芯片断供,经常缺货,麒麟9000已经是一年半以前的芯片了,虽然性能有保障,但还是缺货。华为现在的手机业务2022手机处理器排行榜,前十都是超高性能,换手机前请先了解一下一部手机的性能,主要取决于处理器,处理器越好,则运行起来越流畅。处理器这些年的进步业非常快,从CortexA5到CortexX1,从28nm的制程到现在的4nm制程,处理速度越来越北斗系统助智能终端撬动高精定位市场来源人民网原创稿编者按北斗卫星导航系统是支撑我国经济社会发展的重要空间基础设施,北斗三号系统开通后,提高北斗产业支撑能力扩大北斗应用普及率已成为促进北斗产业发展保障国家安全的重要任别让一元购成为套路购来源中国消费网记者近日调查发现,有多款APP打出一元购0元购的广告,号称支付一元乃至不用花钱就能得到手机等各种商品,或通过下载软件观看广告等可获取免费抽奖机会赢取手机等商品,由此吸东方港湾调研石基信息根据5月9日披露的机构调研信息,知名私募东方港湾对1家上市公司进行了调研,相关名单如下1)石基信息(证券之星综合评级0。5星市盈率216。37)个股亮点蚂蚁金服合作与蚂蚁金服在支付美国学者为什么与中国相比美国在人工智能技术实施方面远远落后以下是一位美国学者,本特林德瓦尔,的观点与看法是的,美国确实落后于很多世界其他地区,而不仅仅是中国。整个中国,十数亿人,人均水平并不领先于美国,但是,许多应用,许多数百人的公司在技
神仙接私活神器,好用的后台管理系统,功能完整,代码结构清晰今天,推荐一个后台管理系统。我第一次使用就有点上头,爱不释手,必须要推荐给大家。上次是谁要的后台管理系统啊,我帮你找到了。这是我目前见过最好的后台管理系统。功能完整,代码结构清晰。最前线华为春季新品发布会发布笔记本打印机平板等7款终端新品文蒲诗钰编辑苏建勋2月27日,华为消费者业务举办线上智慧办公春季发布会,面向全球发布七款终端新品,包括已在国内发布的新一代旗舰笔记本MateBookXPro一体机MateStati汽车软件的春天要来了吗德赛西威无人驾驶域控制器英伟达国内唯一合作商,Xavier芯片以及Orin芯片独家域控供应商。小鹏采用Xavier芯片,理想将采用Orin芯片Orin芯片是英伟达性能最强的车载芯片新能源汽车好,还是燃油车好?假设这个问题是在五年前提出的,那我绝对会毫不犹豫地选择燃油车,因为在那时,新能源汽车只是一个概念,各项技术都还停留在初级阶段,根本无法与燃油车相匹敌。但如果是现在,相信很多人的答案孙佳山认知战已在中国网络空间打响来源环球时报从去年开始,以微博B站等为代表的中国互联网的网络社区,逐渐成为世界各国发布本国外交政策和相关言论等的重要平台。今年伊始,随着乌克兰局势的急剧变化,这一现象更为凸显。相关华为躺平应对制裁华为海思的麒麟系列芯片因漂亮国制裁已经停产两年,华为手机由于断供处理器产量锐减,曾经的荣耀品牌也分离出去独立生存。如此糟糕的处境难道不能破解,束手无策吗?1)华为海思是设计团队,能色域到底是什么?重要吗色域到底是什么随着显示技术的不断进步,色域这个专业名词,开始被广大消费者所接触。购买显示器的时候,经常可以看到有些商家给自己的产品贴上广色域这个标签。那么,真的有必要专门选购广色域国际城市科研创新力报告发布北京上海科研人员数量和科研产出最多2月28日,爱思唯尔联合发布报告,从创新要素科学研究竞争力技术创新竞争力和知识转化竞争力等维度,对全球20个重要城市的城市科研创新力进行比较研究,展示全球城市科技创新全景。该份题为PythonFlask实现接口接收Disk信息今天分享的内容是基于PythonFlask实现接口接收内存信息来进一步分享如何使用PythonFlask实现接收Disk的信息。原理通过Python调用Shell脚本去执行DiskPythonFlask实现接口接收CPU信息今天的内容是基于PythonFlask实现接口接收内存信息来进一步分享如何使用PythonFlask接收CPU的信息。原理通过Python调用Shell脚本去执行CPU的相关信息,没有路由器,没有wifi热点时,怎么上网?大家好,我是王大宝,今天呢,我给小伙伴们分享一则电脑日常使用的小技巧,就是我们如果在单位突然遇到网络故障,没有局域网,没有网络,或者是局域网受限,有很多网站不能够访问,在这种情况下