精华内容
下载资源
问答
  • 2021-08-10 23:59:22

    一级缓存:
    一级缓存也称本地缓存,session级别的缓存,一级缓存是默认开启的,与数据库同一次的会话期间查询到的数据会放在本地缓存中,如果有需要获取相同的数据,则直接从缓存中取,就不会再次查询数据库。在日常的开发中,经常会有相同的sql执行多次的情况,mybatis就提供了一级缓存来优化这些查询,避免多次请求数据库,重点是它的作用域为一次sqlSession会话。

    二级缓存:
    二级缓存是全局缓存,是一个基于namespace级别的缓存,作用域更为广泛,不局限于一个sqlSession,可以在多个sqlSession之间共享,mybatis的二级缓存默认也是开启的,但是由于它的作用域是namespace,所以还需要在mapper.xml中开启才能生效,只需要加入cache标签即可。当要访问数据库形成一个会话查询一条数据时,这个数据就会被放在当前会话的一级缓存中,如果这个时候会话关闭了,那么以及缓存中的数据会被保存到二级缓存中,新的会话查询信息就会参照二级缓存,需要注意的是不同的namespaace会被防止到自己对应的缓存中去。
    小结:
    简单来说就是缓存首先一进来,就会先去二级缓存中查询,如果没有就去一级缓存中查找,一级缓存中没有的话,最后进入数据库中查找。作用顺序为二级缓存>一级缓存>数据库。需要注意的是,一级缓存作用域仅限于同一sqlSession中,无法感知到其他sqlSession中的数据变更,所以极易产生脏数据,而二级缓存可以通过cache-ref让多个mapper.xml共享一个namespace,从而实现缓存共享,但多表联动查询的时候会比较麻烦,由此,在生产环境的时候可以关闭一级缓存(statement级别),有需要的话可以打开二级缓存。如果是分布式应用的话, 这里我们不建议开启,具体原因大家可以了解一下哟~

    更多相关内容
  • 缓存的大小和结构对CPU速度的影响更大,因此缓存的大小也是CPU的重要性能指标之. CPU缓存的工作效率可以远远超过内存和硬盘的速度. 以下安装主页主要介绍有关CPU缓存的知识,有兴趣的用户可能希望学习.什么是CPU...

    47a41460d10490962fbe814d0871a11c.png

    所谓的CPU缓存是CPU内部缓存的运行频率. 缓存的大小和结构对CPU速度的影响更大,因此缓存的大小也是CPU的重要性能指标之一. CPU缓存的工作效率可以远远超过内存和硬盘的速度. 以下安装主页主要介绍有关CPU缓存的知识,有兴趣的用户可能希望学习.

    a87ea2cfcb7c9f5509936b06de79c1ec.png

    什么是CPU缓存,CPU缓存的用途是什么?

    55d312d92f5f557fb34bdb4fbe2d7b7e.png

    CPU高速缓存(英文为Cache Memory)是位于CPU和内存之间的临时内存. CPU缓存的功能主要是解决CPU计算速度与内存读写速度之间的矛盾. 缓存容量比内存小得多,但是其速度比内存快得多,因此这将使CPU占用大量时间来等待数据到达或将数据写入内存. 在高速缓存中搜索的数据仅占内存的一小部分,但是CPU将在短时间内访问这一小部分. 当CPU调用大量数据时,它可以避免内存并直接从缓存中调用它,从而加快读取速度.

    当CPU需要读取数据并执行计算时,它首先需要在CPU缓存中找到所需的数据,并在最短的时间内将其传送到CPU. 如果找不到所需的数据,CPU将“请求”通过高速缓存从内存中读取数据,然后返回CPU进行计算. 同时,数据所在的数据也被传输到缓存中,以便将来可以从缓存中读取整个数据块,而不必调用内存.

    db6e62049b86f7a8bee5513f4ab17cfe.png

    1658467.png

    CPU-CPU缓存-内存可能是此工作状态. 但是,考虑到数据“调度”的进一步优化,CPU缓存也分为几级,例如第一级缓存,第二级缓存,第三级缓存等,主要用于优化吞吐量. 和数据的临时存储,大大提高了执行效率.

    CPU一级缓存,二级缓存和三级缓存是什么意思?

    L1缓存(L1缓存)

    da9352b4a0b32cf9e5fce406d0d0651e.png

    CPU一级缓存是指CPU的一级缓存. 主要工作是缓存指令和数据. 一级缓存的容量和结构对CPU的性能有很大的影响,但是由于它的结构比较复杂,并且考虑到成本等因素,因此通常来说,CPU的一级缓存很小,通常也可以使用CPU的一级缓存. 达到大约256KB的水平.

    二级缓存(L2 Cache66)

    CPU二级缓存是指CPU的二级缓存,二级缓存的容量直接影响CPU的性能. 第二级缓存越大,效果越好. 例如,英特尔的第八代i7-8700处理器共有六个内核,每个内核具有256KB的二级缓存(每个内核专有),因此二级缓存的总数达到1.5MB.

    3fbacd70d4cb690264eab28850dea5.png

    L3缓存(L3缓存)

    CPU三级缓存是指CPU的三级缓存. 它的功能是进一步减少内存的等待时间并提高海量数据的计算性能. 与第一级缓存和第二级缓存不同,第三级缓存由内核共享,这可能使容量非常大.

    fd7b8069b77bb3155596b7bd785595ad.png

    CPU核心的数量和高频会影响性能,但是如果使CPU更智能,更高效地执行计算任务,则缓存的作用至关重要.

    本文来自电脑杂谈,转载请注明本文网址:

    http://www.pc-fly.com/a/shoujiruanjian/article-303355-1.html

    展开全文
  • 大家都知道CPU缓存很重要,但对于缓存的具体细分却知之甚少,本文只要是关于CPU缓存的介绍,并着重描述了一级缓存、二级缓存、三级缓存区别方法。 CPU缓存 CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器...

    大家都知道CPU缓存很重要,但对于缓存的具体细分却知之甚少,本文只要是关于CPU缓存的介绍,并着重描述了一级缓存、二级缓存、三级缓存区别方法。

    CPU缓存

    CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。高速缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可先缓存中调用,从而加快读取速度。
    在这里插入图片描述
    CPU缓存的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。

    缓存大小是CPU的重要指标之一,而且缓存的结构和大小对CPU速度的影响非常大,CPU内缓存的运行频率极高,一般是和处理器同频运作,工作效率远远大于系统内存和硬盘。实际工作时,CPU往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升CPU内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。但是从CPU芯片面积和成本的因素来考虑,缓存都很小。

    按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。

    一级缓存、二级缓存、三级缓存区别是什么?

    一级缓存、二级缓存、三级缓存是什么?作用?区别? 首先简单了解一下一级缓存。目前所有主流处理器大都具有一级缓存和二级缓存,少数高端处理器还集成了三级缓存。其中,一级缓存可分为一级指令缓存和一级数据缓存。一级指令缓存用于暂时存储并向CPU递送各类运算指令;一级数据缓存用于暂时存储并向CPU递送运算所需数据,这就是一级缓存的作用。 那么,二级缓存的作用又是什么呢?简单地说,二级缓存就是一级缓存的缓冲器:一级缓存制造成本很高因此它的容量有限,二级缓存的作用就是存储那些CPU处理时需要用到、一级缓存又无法存储的数据。同样道理,三级缓存和内存可以看作是二级缓存的缓冲器,它们的容量递增,但单位制造成本却递减。

    需要注意的是,无论是二级缓存、三级缓存还是内存都不能存储处理器操作的原始指令,这些指令只能存储在CPU的一级指令缓存中,而余下的二级缓存、三级缓存和内存仅用于存储CPU所需数据。 根据工作原理的不同,目前主流处理器所采用的一级数据缓存又可以分为实数据读写缓存和数据代码指令追踪缓存2种,它们分别被AMD和Intel所采用。不同的一级数据缓存设计对于二级缓存容量的需求也各不相同,下面让我们简单了解一下这两种一级数据缓存设计的不同之处。

    一、AMD一级数据缓存设计 AMD采用的一级缓存设计属于传统的“实数据读写缓存”设计。基于该架构的一级数据缓存主要用于存储CPU最先读取的数据;而更多的读取数据则分别存储在二级缓存和系统内存当中。做个简单的假设,假如处理器需要读取“AMD ATHLON 64 3000+ IS GOOD”这一串数据(不记空格),那么首先要被读取的“AMDATHL”将被存储在一级数据缓存中,而余下的“ON643000+ISGOOD”则被分别存储在二级缓存和系统内存当中(如下图所示)。 需要注意的是,以上假设只是对AMD处理器一级数据缓存的一个抽象描述,一级数据缓存和二级缓存所能存储的数据长度完全由缓存容量的大小决定,而绝非以上假设中的几个字节。“实数据读写缓存”的优点是数据读取直接快速,但这也需要一级数据缓存具有一定的容量,增加了处理器的制造难度(一级数据缓存的单位制造成本较二级缓存高)。

    二、Intel一级数据缓存设计 自P4时代开始,Intel开始采用全新的“数据代码指令追踪缓存”设计。基于这种架构的一级数据缓存不再存储实际的数据,而是存储这些数据在二级缓存中的指令代码(即数据在二级缓存中存储的起始地址)。假设处理器需要读取“INTEL P4 IS GOOD”这一串数据(不记空格),那么所有数据将被存储在二级缓存中,而一级数据代码指令追踪缓存需要存储的仅仅是上述数据的起始地址。

    由于一级数据缓存不再存储实际数据,因此“数据代码指令追踪缓存”设计能够极大地降CPU对一级数据缓存容量的要求,降低处理器的生产难度。但这种设计的弊端在于数据读取效率较“实数据读写缓存设计”低,而且对二级缓存容量的依赖性非常大。 在了解了一级缓存、二级缓存的大致作用及其分类以后,下面我们来回答以下硬件一菜鸟网友提出的问题。

    从理论上讲,二级缓存越大处理器的性能越好,但这并不是说二级缓存容量加倍就能够处理器带来成倍的性能增长。目前CPU处理的绝大部分数据的大小都在0-256KB之间,小部分数据的大小在256KB-512KB之间,只有极少数数据的大小超过512KB。所以只要处理器可用的一级、二级缓存容量达到256KB以上,那就能够应付正常的应用;512KB容量的二级缓存已经足够满足绝大多数应用的需求。 这其中,对于采用“实数据读写缓存”设计的AMD Athlon 64、Sempron处理器而言,由于它们已经具备了64KB一级指令缓存和64KB一级数据缓存,只要处理器的二级缓存容量大于等于128KB就能够存储足够的数据和指令,因此它们对二级缓存的依赖性并不大。这就是为什么主频同为1.8GHz的Socket 754 Sempron 3000+(128KB二级缓存)、Sempron 3100+(256KB二级缓存)以及Athlon 64 2800+(512KB二级缓存)在大多数评测中性能非常接近的主要原因。所以对于普通用户而言754 Sempron 2600+是值得考虑的。 反观Intel目前主推的P4、赛扬系列处理器,它们都采用了“数据代码指令追踪缓存”架构,其中Prescott内核的一级缓存中只包含了12KB一级指令缓存和16KB一级数据缓存,而Northwood内核更是只有12KB一级指令缓存和8KB一级数据缓存。

    因此,P4、赛隆系列处理器非常依赖于二级缓存,赛扬D 320(256KB二级缓存)和赛扬2.4GHz(128kb二级缓存)的性能差距是很好的证明;Cayon D和P4E处理器之间的性能差距也非常明显。最后,如果你是一个狂热的游戏爱好者或者一个专业的多媒体用户,一个带有1MB二级缓存的P4处理器和一个512Kb/1MB二级缓存的Athon 64处理器是你的理想选择。由于CPU的主存和二级缓存在重计算负载下几乎是“满”的,大的二级缓存可以为处理器提供大约5%到10%的性能改进,这对于要求苛刻的用户是绝对必要的。一级缓存是在CPU内的,用来存放内部指令,2级缓存和CPU封装在一起,也是用来存放指令数据的,三级和四级缓存只在高端的服务器CPU里有,作用差不多,速度更快,更稳定,更有效 并不是缓存越大越好,譬如AMD和INTER就有不同的理论,AMD认为一级缓存越大越好,所以一级比较大,而INTER认为过大会有更长的指令执行时间,所以一级很小,二级缓存那两个公司的理论又反过来了,AMD的小,INTER的大,一般主流的INTERCPU的2级缓存都在2M左右 我们通常用(L1,L2)来称呼缓存又叫高速缓冲存储器其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。它的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当CPU下次再用这些信息时,它就不必访问慢速的主存,而直接从快速的CACHE中得到,从而提高了得到这些信息的速度,使CPU有更高的运行效率。

    缓存的大小

    一般说来,更大一点的cache容量,对提高命中率是有好处的,如图4.20所示,由于cache 是用价格很高的静态存储器SRAM器件实现的,而cache容量达到一定大小这后,再增加其容量,对命中率的提高并不明显,从合理的性能/价格比考虑,cache的容量设置应在一个合理的容量范围之内。缓存要分一级二级 三级,是为了建立一个层次存储结构,以达到最高性价比。而且多级组织还可以提高cache的命中率,提高执行效能。

    CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。由此可见,在CPU中加入缓存是一种高效的解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速度,又有内存的大容量的存储系统了。缓存对CPU的性能影响很大,主要是因为CPU的数据交换顺序和CPU与缓存间的带宽引起的。 缓存的工作原理是当CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。 正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在缓存中,只有大约10%需要从内存读取。

    这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存。 目前缓存基本上都是采用SRAM存储器,SRAM是英文Static RAM的缩写,它是一种具有静志存取功能的存储器,不需要刷新电路即能保存它内部存储的数据。不像DRAM内存那样需要刷新电路,每隔一段时间,固定要对DRAM刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积,这也是目前不能将缓存容量做得太大的重要原因。

    它的特点归纳如下:优点是节能、速度快、不必配合内存刷新电路、可提高整体的工作效率,缺点是集成度低、相同的容量体积较大、而且价格较高,只能少量用于关键性系统以提高效率。 按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。

    一级缓存(Level 1 Cache)简称L1 Cache,位于CPU内核的旁边,是与CPU结合最为紧密的CPU缓存,也是历史上最早出现的CPU缓存。由于一级缓存的技术难度和制造成本最高,提高容量所带来的技术难度增加和成本增加非常大,所带来的性能提升却不明显,性价比很低,而且现有的一级缓存的命中率已经很高,所以一级缓存是所有缓存中容量最小的,比二级缓存要小得多。一般来说,一级缓存可以分为一级数据缓存(Data Cache,D-Cache)和一级指令缓存(InstrucTIon Cache,I-Cache)。二者分别用来存放数据以及对执行这些数据的指令进行即时解码,而且两者可以同时被CPU访问,减少了争用Cache所造成的冲突,提高了处理器效能。目前大多数CPU的一级数据缓存和一级指令缓存具有相同的容量,例如AMD的Athlon XP就具有64KB的一级数据缓存和64KB的一级指令缓存,其一级缓存就以64KB+64KB来表示,其余的CPU的一级缓存表示方法以此类推。

    Intel的采用NetBurst架构的CPU(最典型的就是PenTIum 4)的一级缓存有点特殊,使用了新增加的一种一级追踪缓存(ExecuTIon Trace Cache,T-Cache或ETC)来替代一级指令缓存,容量为12KμOps,表示能存储12K条即12000条解码后的微指令。一级追踪缓存与一级指令缓存的运行机制是不相同的,一级指令缓存只是对指令作即时的解码而并不会储存这些指令,而一级追踪缓存同样会将一些指令作解码,这些指令称为微指令(micro-ops),而这些微指令能储存在一级追踪缓存之内,无需每一次都作出解码的程序,因此一级追踪缓存能有效地增加在高工作频率下对指令的解码能力,而μOps就是micro-ops,也就是微型操作的意思。它以很高的速度将μops提供给处理器核心。Intel NetBurst微型架构使用执行跟踪缓存,将解码器从执行循环中分离出来。

    这个跟踪缓存以很高的带宽将uops提供给核心,从本质上适于充分利用软件中的指令级并行机制。Intel并没有公布一级追踪缓存的实际容量,只知道一级追踪缓存能储存12000条微指令(micro-ops)。所以,我们不能简单地用微指令的数目来比较指令缓存的大小。实际上,单核心的NetBurst架构CPU使用8Kμops的缓存已经基本上够用了,多出的4kμops可以大大提高缓存命中率。而如果要使用超线程技术的话,12KμOps就会有些不够用,这就是为什么有时候Intel处理器在使用超线程技术时会导致性能下降的重要原因。

    例如Northwood核心的一级缓存为8KB+12KμOps,就表示其一级数据缓存为8KB,一级追踪缓存为12KμOps;而Prescott核心的一级缓存为16KB+12KμOps,就表示其一级数据缓存为16KB,一级追踪缓存为12KμOps。在这里12KμOps绝对不等于12KB,单位都不同,一个是μOps,一个是Byte(字节),而且二者的运行机制完全不同。所以那些把Intel的CPU一级缓存简单相加,例如把Northwood核心说成是20KB一级缓存,把Prescott核心说成是28KB一级缓存,并且据此认为Intel处理器的一级缓存容量远远低于AMD处理器128KB的一级缓存容量的看法是完全错误的,二者不具有可比性。在架构有一定区别的CPU对比中,很多缓存已经难以找到对应的东西,即使类似名称的缓存在设计思路和功能定义上也有区别了,此时不能用简单的算术加法来进行对比;而在架构极为近似的CPU对比中,分别对比各种功能缓存大小才有一定的意义。

    转载自(http://www.elecfans.com/bandaoti/cunchu/20180814728879.html)

    展开全文
  • MyBatis一级缓存和二级缓存

    千次阅读 2022-04-03 20:36:19
    Mybatis缓存

    目录

    为什么要用缓存

    一级缓存

    一级缓存的生命周期

    一级缓存失效情况

    二级缓存

    二级缓存失效

    自定义缓存

    Cache组件

    PerpetualCache

    BlockingCache

    FifoCache和LruCache

    SoftCache和WeakCache

    CacheKey


    为什么要用缓存

    在计算机的世界中,缓存无处不在,操作系统有操作系统的缓存,数据库也会有数据库的缓存,各种中间件如Redis也是用来充当缓存的作用,编程语言中又可以利用内存来作为缓存。 MyBatis作为一款优秀的ORM框架,也用到了缓存,本文的目的就是探究一下MyBatis的缓存是如何实现的。尤其是I/O操作,除了那种CPU密集型的系统,其余大部分的业务系统性能瓶颈最后或多或少都会出现在I/O操作上,所以为了减少磁盘的I/O次数,缓存是必不可少的,通过缓存的使用我们可以大大减少I/O操作次数,从而在一定程度上弥补了I/O操作和CPU处理速度之间的鸿沟。在ORM框架中引入缓存的目的就是为了减少读取数据库的次数,从而提升查询的效率。

    一级缓存

    前面了解了SqlSessionFactory、SqlSession、Excutor以及Mpper执行SQL过程,下面来了解下myabtis的缓存,它的缓存分为一级缓存和二级缓存。 使用MyBatis开启一次和数据库的会话,MyBatis会创建出一个SqlSession对象表示一次数据库会话,在对数据库的一次会话中, 有可能会反复地执行完全相同的查询语句,每一次查询都会去查一次数据库,为了减少资源浪费,mybaits提供了一种缓存的方式(一级缓存)。 mybatis的SQL执行最后是交给了Executor执行器来完成的,看下BaseExecutor类的源码:

     @Override
        public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
            BoundSql boundSql = ms.getBoundSql(parameter);
            CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
            return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
        }
    
        @SuppressWarnings("unchecked")
        @Override
        public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
            ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
            if (closed) {
                throw new ExecutorException("Executor was closed.");
            }
            if (queryStack == 0 && ms.isFlushCacheRequired()) {
                clearLocalCache();
            }
            List<E> list;
            try {
                queryStack++;
                list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//localCache 本地缓存
                if (list != null) {
                    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);  //如果缓存没有就走DB
                }
            } finally {
                queryStack--;
            }
            if (queryStack == 0) {
                for (DeferredLoad deferredLoad : deferredLoads) {
                    deferredLoad.load();
                }
                // issue #601
                deferredLoads.clear();
                if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    // issue #482
                    clearLocalCache();
                }
            }
            return list;
        }
    
        private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
            List<E> list;
            localCache.putObject(key, EXECUTION_PLACEHOLDER);
            try {
                list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
            } finally {
                localCache.removeObject(key);//清空现有缓存数据
            }
            localCache.putObject(key, list);//新的结果集存入缓存
            if (ms.getStatementType() == StatementType.CALLABLE) {
                localOutputParameterCache.putObject(key, parameter);
            }
            return list;
        }

    它的本地缓存使用的是PerpetualCache类,内部是一个HashMap作了一个封装来存数据。缓存Key的生成:

    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    
        @Override
        public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
            if (closed) {
                throw new ExecutorException("Executor was closed.");
            }
            CacheKey cacheKey = new CacheKey();
            cacheKey.update(ms.getId());
            cacheKey.update(Integer.valueOf(rowBounds.getOffset()));
            cacheKey.update(Integer.valueOf(rowBounds.getLimit()));
            cacheKey.update(boundSql.getSql());
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
            // mimic DefaultParameterHandler logic
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        value = parameterObject;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    cacheKey.update(value);
                }
            }
            if (configuration.getEnvironment() != null) {
                // issue #176
                cacheKey.update(configuration.getEnvironment().getId());
            }
            return cacheKey;
        }

    通过statementId,params,rowBounds,BoundSql来构建一个key值,根据这个key值去缓存Cache中取出对应缓存结果。

    一级缓存的生命周期

    比如要执行一个查询操作时,Mybatis会创建一个新的SqlSession对象,SqlSession对象找到具体的Executor, Executor持有一个PerpetualCache对象;当查询结束(会话结束)时,SqlSession、Executor、PerpetualCache对象占有的资源一并释放掉。

    如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

    如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

    SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

    一级缓存失效情况

    注意:一级缓存为sqlSession级别的缓存,默认开启的,不能关闭。一级缓存失效的四种情况:
    1)sqlSession不同,缓存失效。
    2)sqlSession相同,查询条件不同,缓存失效,因为缓存中可能还没有相关数据。
    3)sqlSession相同,在两次查询期间,执行了增删改操作,缓存失效。
    4)sqlSession相同,但是手动清空了一级缓存,缓存失效。
    清除缓存情况:
    1、就是获取缓存之前会先进行判断用户是否配置了flushCache=true属性(参考一级缓存的创建代码截图),
    如果配置了则会清除一级缓存。
    2、MyBatis全局配置属性localCacheScope配置为Statement时,那么完成一次查询就会清除缓存。
    3、在执行commit,rollback,update方法时会清空一级缓存
    

    二级缓存

    Mybatis默认对二级缓存是关闭的,一级缓存默认开启,如果需要开启只需在mapper上加入配置就好了。Executor是执行查询的最终接口,它有两个实现类一个是BaseExecutor另外一个是CachingExecutor。CachingExecutor(二级缓存查询),一级缓存因为只能在同一个SqlSession中共享,所以会存在一个问题,在分布式或者多线程的环境下,不同会话之间对于相同的数据可能会产生不同的结果,因为跨会话修改了数据是不能互相感知的,所以就有可能存在脏数据的问题,正因为一级缓存存在这种不足,需要一种作用域更大的缓存,这就是二级缓存。
    CachingExecutor实现类里面的query查询方法:

    @Override
    ublic List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
                throws SQLException {
            Cache cache = ms.getCache();//二级缓存对象
            if (cache != null) {
                flushCacheIfRequired(ms);
                if (ms.isUseCache() && resultHandler == null) {
                    ensureNoOutParams(ms, parameterObject, boundSql);
                    @SuppressWarnings("unchecked")
                    List list = (List) tcm.getObject(cache, key);//从缓存中读取
                    if (list == null) {
                        //这段走到一级缓存或者DB
                        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                        tcm.putObject(cache, key, list); // issue #578 and #116  //数据放入缓存
                    }
                    return list;
                }
            }
            return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    一个事务方法运行时,数据查询出来,缓存在一级缓存了,但是没有到二级缓存,当事务提交后(sqlSession.commit()),数据才放到二级缓存。查询的顺序是,先查二级缓存再查一级缓存然后才去数据库查询。

    一级缓存作用域是SqlSession级别,所以它存储的SqlSession中的BaseExecutor之中,但是二级缓存目的要实现作用范围更广,所以要实现跨会话共享,MyBatis二级缓存的作用域是namespace,专门用了一个装饰器来维护,这就是:CachingExecutor。

          二级缓存相关的配置有三个地方:
          1、mybatis-config中有一个全局配置属性,这个不配置也行,因为默认就是true。
          <setting name="cacheEnabled" value="true"/>
          想详细了解mybatis-config的可以点击这里。
          2、在Mapper映射文件内需要配置缓存标签:
          <cache/>
          或
          <cache-ref namespace="com.lonelyWolf.mybatis.mapper.UserAddressMapper"/>
          想详细了解Mapper映射的所有标签属性配置可以点击这里。
          3、在select查询语句标签上配置useCache属性,如下:
          <select id="selectUserAndJob" resultMap="JobResultMap2" useCache="true">
              select * from lw_user
          </select>
          以上配置第1点是默认开启的,也就是说我们只要配置第2点就可以打开二级缓存了,
          而第3点是当我们需要针对某一条语句来配置二级缓存时候则可以使用。
          1、需要commit事务之后才会生效
          2、如果使用的是默认缓存,那么结果集对象需要实现序列化接口(Serializable)
          如果不实现序列化接口则会报如下错误
          
          * 二级缓存工作机制(?)
          1)一个会话,查询一条数据,该数据会放在当前会话的一级缓存中。
          2)如果当前会话关闭,对应的一级缓存会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存。
          3)不同namespace查询出的数据会放在自己对应的缓存中。
          注意:查出的数据都会默认放在一级缓存中,只有会话提交或关闭后,一级缓存的数据才会被转移到二级缓存中。
          
          * 需要注意的是在事务提交之前,并不会真正存储到二级缓存,而是先存储到一个临时属性,
          * 等事务提交之后才会真正存储到二级缓存。(?)
          Mybatis缓存包装汇总:
          PerpetualCache	缓存默认实现类	-	基本功能,默认携带
          LruCache	LRU淘汰策略缓存(默认淘汰策略)	当缓存达到上限,删除最近最少使用缓存	eviction=“LRU”
          FifoCache	FIFO淘汰策略缓存	当缓存达到上限,删除最先入队的缓存	eviction=“FIFO”
          SoftCache	JVM软引用淘汰策略缓存	基于JVM的SoftReference对象	eviction=“SOFT”
          WeakCache	JVM弱引用淘汰策略缓存	基于JVM的WeakReference对象	eviction=“WEAK”
          LoggingCache	带日志功能缓存	输出缓存相关日志信息	基本功能,默认包装
          SynchronizedCache	同步缓存	基于synchronized关键字实现,用来解决并发问题	基本功能,默认包装
          BlockingCache	阻塞缓存	get/put操作时会加锁,防止并发,基于Java重入锁实现	blocking=true
          SerializedCache	支持序列化的缓存	通过序列化和反序列化来存储和读取缓存	readOnly=false(默认)
          ScheduledCache	定时调度缓存	操作缓存时如果缓存已经达到了设置的最长缓存时间时会移除缓存	flushInterval属性不为空
          TransactionalCache	事务缓存	在TransactionalCacheManager中用于维护缓存map的value值	
    

    二级缓存失效

    所有的update操作(insert,delete,uptede)都会触发缓存的刷新,从而导致二级缓存失效,所以二级缓存适合在读多写少的场景中开启。

    二级缓存针对的是同一个namespace,所以建议是在单表操作的Mapper中使用,或者是在相关表的Mapper文件中共享同一个缓存。

    自定义缓存

    一级缓存可能存在脏读情况,那么二级缓存是否也可能存在呢?是的,默认的二级缓存也是存储在本地缓存,对于微服务下是可能出现脏读的情况的,这时可能会需要自定义缓存,比如利用redis来存储缓存,而不是存储在本地内存当中。

          MyBatis官方也提供了第三方缓存的支持引入pom文件:
          <dependency>
              <groupId>org.mybatis.caches</groupId>
              <artifactId>mybatis-redis</artifactId>
              <version>1.0.0-beta2</version>
          </dependency>
          然后缓存配置如下:
          <cache type="org.mybatis.caches.redis.RedisCache"></cache>
          然后在默认的resource路径下新建一个redis.properties文件:
          host=localhost
          port=6379
    

    延迟加载原理

    延迟加载原理:调用的时候触发加载,不是在初始化的时候就加载信息。MyBatis支持延迟加载,设置lazyLoadingEnabled=true即可。

          比如:a.getB().getName(),发现a.getB()的值为null,此时会单独触发事件,将保存好的关联B对象的SQL查询出来,
          然后再调用a.setB(b),这时再调用a.getB().getName()就有值了。

    Cache组件

    MyBatis 中缓存模块相关的代码位于 org.apache.ibatis.cache 包 下,其中 Cache 接口 是缓存模块中最核心的接口,它定义了所有缓存的基本行为。

    public interface Cache {
    
      /**
       * 获取当前缓存的 Id
       */
      String getId();
    
      /**
       * 存入缓存的 key 和 value,key 一般为 CacheKey对象
       */
      void putObject(Object key, Object value);
    
      /**
       * 根据 key 获取缓存值
       */
      Object getObject(Object key);
    
      /**
       * 删除指定的缓存项
       */
      Object removeObject(Object key);
    
      /**
       * 清空缓存
       */
      void clear();
    
      /**
       * 获取缓存的大小
       */
      int getSize();
    
      /**
       * !!!!!!!!!!!!!!!!!!!!!!!!!!
       * 获取读写锁,可以看到,这个接口方法提供了默认的实现!!
       * 这是 Java8 的新特性!!只是平时开发时很少用到!!!
       * !!!!!!!!!!!!!!!!!!!!!!!!!!
       */
      default ReadWriteLock getReadWriteLock() {
        return null;
      }
    }

    Cache接口的实现类有很多,大部分都是装饰器,只有PerpetualCache 提供了 Cache 接口 的基本实现

    PerpetualCache

    PerpetualCache(Perpetual:永恒的,持续的)在缓存模块中扮演着被装饰的角色,其实现比较简单,底层使用 HashMap 记录缓存项,也是通过该 HashMap 对象 的方法实现的 Cache 接口 中定义的相应方法。

    public class PerpetualCache implements Cache {
    
      // Cache对象 的唯一标识
      private final String id;
    
      // 其所有的缓存功能实现,都是基于 JDK 的 HashMap 提供的方法
      private Map<Object, Object> cache = new HashMap<>();
    
      public PerpetualCache(String id) {
        this.id = id;
      }
    
      @Override
      public String getId() {
        return id;
      }
    
      @Override
      public int getSize() {
        return cache.size();
      }
    
      @Override
      public void putObject(Object key, Object value) {
        cache.put(key, value);
      }
    
      @Override
      public Object getObject(Object key) {
        return cache.get(key);
      }
    
      @Override
      public Object removeObject(Object key) {
        return cache.remove(key);
      }
    
      @Override
      public void clear() {
        cache.clear();
      }
    
      /**
       * 其重写了 Object 中的 equals() 和 hashCode()方法,两者都只关心 id字段
       */
      @Override
      public boolean equals(Object o) {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        if (this == o) {
          return true;
        }
        if (!(o instanceof Cache)) {
          return false;
        }
    
        Cache otherCache = (Cache) o;
        return getId().equals(otherCache.getId());
      }
    
      @Override
      public int hashCode() {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        return getId().hashCode();
      }
    }

    cache.decorators包下提供的装饰器,它们都直接实现了Cache接口,扮演着装饰器的角色。这些装饰器会在 PerpetualCache 的基础上提供一些额外的功能,通过多个组合后满足一个特定的需求。

    BlockingCache

    BlockingCache 是阻塞版本的缓存装饰器,它会保证只有一个线程到数据库中查找指定 key 对应的数据

    public class BlockingCache implements Cache {
    
      // 阻塞超时时长
      private long timeout;
      // 持有的被装饰者
      private final Cache delegate;
      // 每个 key 都有其对应的 ReentrantLock锁对象
      private final ConcurrentHashMap<Object, ReentrantLock> locks;
    
      // 初始化 持有的持有的被装饰者 和 锁集合
      public BlockingCache(Cache delegate) {
        this.delegate = delegate;
        this.locks = new ConcurrentHashMap<>();
      }
    }

    假设线程A在BlockingCache中未查找到 keyA 对应的缓存项时,线程 A 会获取 keyA 对应的锁,这样,线程 A 在后续查找 keyA 时,其它线程会被阻塞。

    // 根据 key 获取锁对象,然后上锁
      private void acquireLock(Object key) {
        // 获取 key 对应的锁对象
        Lock lock = getLockForKey(key);
        // 获取锁,带超时时长
        if (timeout > 0) {
          try {
            boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
            if (!acquired) { // 超时,则抛出异常
              throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());
            }
          } catch (InterruptedException e) {
            // 如果获取锁失败,则阻塞一段时间
            throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
          }
        } else {
          // 上锁
          lock.lock();
        }
      }
    
      private ReentrantLock getLockForKey(Object key) {
        // Java8 新特性,Map系列类 中新增的方法
        // V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
        // 表示,若 key 对应的 value 为空,则将第二个参数的返回值存入该 Map集合 并返回
        return locks.computeIfAbsent(key, k -> new ReentrantLock());
      }

    假设 线程 A 从数据库中查找到 keyA 对应的结果对象后,将结果对象放入到 BlockingCache 中,此时 线程 A 会释放 keyA 对应的锁,唤醒阻塞在该锁上的线程。其它线程即可从 BlockingCache 中获取 keyA 对应的数据,而不是再次访问数据库。

     @Override
      public void putObject(Object key, Object value) {
        try {
          // 存入 key 和其对应的缓存项
          delegate.putObject(key, value);
        } finally {
          // 最后释放锁
          releaseLock(key);
        }
      }
    
      private void releaseLock(Object key) {
        ReentrantLock lock = locks.get(key);
        // 锁是否被当前线程持有
        if (lock.isHeldByCurrentThread()) {
          // 是,则释放锁
          lock.unlock();
        }
      }

    FifoCache和LruCache

    为了控制缓存的大小,系统需要按照一定的规则清理缓存。FifoCache 是先入先出版本的装饰器,当向缓存添加数据时,如果缓存项的个数已经达到上限,则会将缓存中最老(即最早进入缓存)的缓存项删除。

    public class FifoCache implements Cache {
    
      // 被装饰对象
      private final Cache delegate;
      // 用一个 FIFO 的队列记录 key 的顺序,其具体实现为 LinkedList
      private final Deque<Object> keyList;
      // 决定了缓存的容量上限
      private int size;
    
      // 国际惯例,通过构造方法初始化自己的属性,缓存容量上限默认为 1024个
      public FifoCache(Cache delegate) {
        this.delegate = delegate;
        this.keyList = new LinkedList<>();
        this.size = 1024;
      }
    
      @Override
      public String getId() {
        return delegate.getId();
      }
    
      @Override
      public int getSize() {
        return delegate.getSize();
      }
    
      public void setSize(int size) {
        this.size = size;
      }
    
      @Override
      public void putObject(Object key, Object value) {
        // 存储缓存项之前,先在 keyList 中注册
        cycleKeyList(key);
        // 存储缓存项
        delegate.putObject(key, value);
      }
    
      private void cycleKeyList(Object key) {
        // 在 keyList队列 中注册要添加的 key
        keyList.addLast(key);
        // 如果注册这个 key 会超出容积上限,则把最老的一个缓存项清除掉
        if (keyList.size() > size) {
          Object oldestKey = keyList.removeFirst();
          delegate.removeObject(oldestKey);
        }
      }
    
      @Override
      public Object getObject(Object key) {
        return delegate.getObject(key);
      }
    
      @Override
      public Object removeObject(Object key) {
        return delegate.removeObject(key);
      }
    
      // 除了清理缓存项,还要清理 key 的注册列表
      @Override
      public void clear() {
        delegate.clear();
        keyList.clear();
      }
    
    }

    LruCache 是按照"近期最少使用算法"(Least Recently Used, LRU)进行缓存清理的装饰器,在需要清理缓存时,它会清除最近最少使用的缓存项。

    public class LruCache implements Cache {
    
      // 被装饰者
      private final Cache delegate;
      // 这里使用的是 LinkedHashMap,它继承了 HashMap,但它的元素是有序的
      private Map<Object, Object> keyMap;
      // 最近最少被使用的缓存项的 key
      private Object eldestKey;
    
      // 国际惯例,构造方法中进行属性初始化
      public LruCache(Cache delegate) {
        this.delegate = delegate;
        // 这里初始化了 keyMap,并定义了 eldestKey 的取值规则
        setSize(1024);
      }
    
      public void setSize(final int size) {
        // 初始化 keyMap,同时指定该 Map 的初始容积及加载因子,第三个参数true 表示 该LinkedHashMap
        // 记录的顺序是 accessOrder,即,LinkedHashMap.get()方法 会改变其中元素的顺序
        keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
          private static final long serialVersionUID = 4267176411845948333L;
    
          // 当调用 LinkedHashMap.put()方法 时,该方法会被调用
          @Override
          protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
            boolean tooBig = size() > size;
            if (tooBig) {
              // 当已达到缓存上限,更新 eldestKey字段,后面将其删除
              eldestKey = eldest.getKey();
            }
            return tooBig;
          }
        };
      }
    
      // 存储缓存项
      @Override
      public void putObject(Object key, Object value) {
        delegate.putObject(key, value);
        // 记录缓存项的 key,超出容量则清除最久未使用的缓存项
        cycleKeyList(key);
      }
    
      private void cycleKeyList(Object key) {
        keyMap.put(key, key);
        // eldestKey 不为空,则表示已经达到缓存上限
        if (eldestKey != null) {
          // 清除最久未使用的缓存
          delegate.removeObject(eldestKey);
          // 制空
          eldestKey = null;
        }
      }
    
      @Override
      public Object getObject(Object key) {
        // 访问 key元素 会改变该元素在 LinkedHashMap 中的顺序
        keyMap.get(key); //touch
        return delegate.getObject(key);
      }
    
      @Override
      public String getId() {
        return delegate.getId();
      }
    
      @Override
      public int getSize() {
        return delegate.getSize();
      }
    
      @Override
      public Object removeObject(Object key) {
        return delegate.removeObject(key);
      }
    
      @Override
      public void clear() {
        delegate.clear();
        keyMap.clear();
      }
    
    }

    SoftCache和WeakCache

    在分析 SoftCache 和 WeakCache 实现之前,我们再温习一下 Java 提供的 4 种引用类型,强引用 StrongReference、软引用 SoftReference、弱引用 WeakReference 和虚引用 PhantomReference。

    • 强引用 平时用的最多的,如 Object obj = new Object(),新建的 Object 对象 就是被强引用的。如果一个对象被强引用,即使是 JVM 内存空间不足,要抛出 OutOfMemoryError 异常,GC 也绝不会回收该对象。
    • 软引用 仅次于强引用的一种引用,它使用类 SoftReference 来表示。当 JVM 内存不足时,GC 会回收那些只被软引用指向的对象,从而避免内存溢出。软引用适合引用那些可以通过其他方式恢复的对象,例如, 数据库缓存中的对象就可以从数据库中恢复,所以软引用可以用来实现缓存,下面要介绍的 SoftCache 就是通过软引用实现的。
      另外,由于在程序使用软引用之前的某个时刻,其所指向的对象可能己经被 GC 回收掉了,所以通过 Reference.get()方法 来获取软引用所指向的对象时,总是要通过检查该方法返回值是否为 null,来判断被软引用的对象是否还存活。
    • 弱引用 弱引用使用 WeakReference 表示,它不会阻止所引用的对象被 GC 回收。在 JVM 进行垃圾回收时,如果指向一个对象的所有引用都是弱引用,那么该对象会被回收。 所以,只被弱引用所指向的对象,其生存周期是 两次 GC 之间 的这段时间,而只被软引用所指向的对象可以经历多次 GC,直到出现内存紧张的情况才被回收。
    • 虚引用 最弱的一种引用类型,由类 PhantomReference 表示。虚引用可以用来实现比较精细的内存使用控制,但很少使用。
    • 引用队列(ReferenceQueue ) 很多场景下,我们的程序需要在一个对象被 GC 时得到通知,引用队列就是用于收集这些信息的队列。在创建 SoftReference 对象 时,可以为其关联一个引用队列,当 SoftReference 所引用的对象被 GC 时, JVM 就会将该 SoftReference 对象 添加到与之关联的引用队列中。当需要检测这些通知信息时,就可以从引用队列中获取这些 SoftReference 对象。不仅是 SoftReference,弱引用和虚引用都可以关联相应的队列。

    SoftCache 的具体实现

    public class SoftCache implements Cache {
    
      // 这里使用了 LinkedList 作为容器,在 SoftCache 中,最近使用的一部分缓存项不会被 GC
      // 这是通过将其 value 添加到 hardLinksToAvoidGarbageCollection集合 实现的(即,有强引用指向其value)
      private final Deque<Object> hardLinksToAvoidGarbageCollection;
      // 引用队列,用于记录已经被 GC 的缓存项所对应的 SoftEntry对象
      private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
      // 持有的被装饰者
      private final Cache delegate;
      // 强连接的个数,默认为 256
      private int numberOfHardLinks;
    
      // 构造方法进行属性的初始化
      public SoftCache(Cache delegate) {
        this.delegate = delegate;
        this.numberOfHardLinks = 256;
        this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
        this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
      }
    
      private static class SoftEntry extends SoftReference<Object> {
        private final Object key;
    
        SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
          // 指向 value 的引用是软引用,并且关联了 引用队列
          super(value, garbageCollectionQueue);
          // 强引用
          this.key = key;
        }
      }
    
      @Override
      public void putObject(Object key, Object value) {
        // 清除已经被 GC 的缓存项
        removeGarbageCollectedItems();
        // 添加缓存
        delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
      }
    
      private void removeGarbageCollectedItems() {
        SoftEntry sv;
        // 遍历 queueOfGarbageCollectedEntries集合,清除已经被 GC 的缓存项 value
        while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
          delegate.removeObject(sv.key);
        }
      }
    
      @Override
      public Object getObject(Object key) {
        Object result = null;
        @SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
          // 用一个软引用指向 key 对应的缓存项
          SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
        // 检测缓存中是否有对应的缓存项
        if (softReference != null) {
          // 获取 softReference 引用的 value
          result = softReference.get();
          // 如果 softReference 引用的对象已经被 GC,则从缓存中清除对应的缓存项
          if (result == null) {
            delegate.removeObject(key);
          } else {
            synchronized (hardLinksToAvoidGarbageCollection) {
              // 将缓存项的 value 添加到 hardLinksToAvoidGarbageCollection集合 中保存
              hardLinksToAvoidGarbageCollection.addFirst(result);
              // 如果 hardLinksToAvoidGarbageCollection 的容积已经超过 numberOfHardLinks
              // 则将最老的缓存项从 hardLinksToAvoidGarbageCollection 中清除,FIFO
              if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
                hardLinksToAvoidGarbageCollection.removeLast();
              }
            }
          }
        }
        return result;
      }
    
      @Override
      public Object removeObject(Object key) {
        // 清除指定的缓存项之前,也会先清理被 GC 的缓存项
        removeGarbageCollectedItems();
        return delegate.removeObject(key);
      }
    
    
      @Override
      public void clear() {
        synchronized (hardLinksToAvoidGarbageCollection) {
          // 清理强引用集合
          hardLinksToAvoidGarbageCollection.clear();
        }
        // 清理被 GC 的缓存项
        removeGarbageCollectedItems();
        // 清理最底层的缓存项
        delegate.clear();
      }
    
      @Override
      public String getId() {
        return delegate.getId();
      }
    
      @Override
      public int getSize() {
        removeGarbageCollectedItems();
        return delegate.getSize();
      }
    
      public void setSize(int size) {
        this.numberOfHardLinks = size;
      }
    
    }

    WeakCache 的实现与 SoftCache 基本类似,唯一的区别在于其中使用 WeakEntry(继承了 WeakReference)封装真正的 value 对象,其他实现完全一样。

    另外,还有 ScheduledCache、LoggingCache、SynchronizedCache、SerializedCache 等。ScheduledCache 是周期性清理缓存的装饰器,它的 clearInterval 字段 记录了两次缓存清理之间的时间间隔,默认是一小时,lastClear 字段 记录了最近一次清理的时间戳。 ScheduledCache 的 getObject()、putObject()、removeObject() 等核心方法,在执行时都会根据这两个字段检测是否需要进行清理操作,清理操作会清空缓存中所有缓存项。

    LoggingCache 在 Cache 的基础上提供了日志功能,它通过 hit 字段 和 request 字段 记录了 Cache 的命中次数和访问次数。在 LoggingCache.getObject()方法 中,会统计命中次数和访问次数 这两个指标,井按照指定的日志输出方式输出命中率。

    SynchronizedCache 通过在每个方法上添加 synchronized 关键字,为 Cache 添加了同步功能,有点类似于 JDK 中 Collections 的 SynchronizedCollection 内部类。

    SerializedCache 提供了将 value 对象 序列化的功能。SerializedCache 在添加缓存项时,会将 value 对应的 Java 对象 进行序列化,井将序列化后的 byte[]数组 作为 value 存入缓存 。 SerializedCache 在获取缓存项时,会将缓存项中的 byte[]数组 反序列化成 Java 对象。不使用 SerializedCache 装饰器 进行装饰的话,每次从缓存中获取同一 key 对应的对象时,得到的都是同一对象,任意一个线程修改该对象都会影响到其他线程,以及缓存中的对象。而使用 SerializedCache 每次从缓存中获取数据时,都会通过反序列化得到一个全新的对象。 SerializedCache 使用的序列化方式是 Java 原生序列化。

    CacheKey

    在 Cache 中唯一确定一个缓存项,需要使用缓存项的 key 进行比较,MyBatis 中因为涉及 动态 SQL 等多方面因素, 其缓存项的 key 不能仅仅通过一个 String 表示,所以 MyBatis 提供了 CacheKey 类 来表示缓存项的 key,在一个 CacheKey 对象 中可以封装多个影响缓存项的因素。 CacheKey 中可以添加多个对象,由这些对象共同确定两个 CacheKey 对象 是否相同。

    public class CacheKey implements Cloneable, Serializable {
    
      private static final long serialVersionUID = 1146682552656046210L;
    
      public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
    
      private static final int DEFAULT_MULTIPLYER = 37;
      private static final int DEFAULT_HASHCODE = 17;
    
      // 参与计算hashcode,默认值DEFAULT_MULTIPLYER = 37
      private final int multiplier;
      // 当前CacheKey对象的hashcode,默认值DEFAULT_HASHCODE = 17
      private int hashcode;
      // 校验和
      private long checksum;
      private int count;
    
      // 由该集合中的所有元素 共同决定两个CacheKey对象是否相同,一般会使用一下四个元素
      // MappedStatement的id、查询结果集的范围参数(RowBounds的offset和limit)
      // SQL语句(其中可能包含占位符"?")、SQL语句中占位符的实际参数
      private List<Object> updateList;
    
      // 构造方法初始化属性
      public CacheKey() {
        this.hashcode = DEFAULT_HASHCODE;
        this.multiplier = DEFAULT_MULTIPLYER;
        this.count = 0;
        this.updateList = new ArrayList<>();
      }
    
      public CacheKey(Object[] objects) {
        this();
        updateAll(objects);
      }
    
      public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
        // 重新计算count、checksum和hashcode的值
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
        hashcode = multiplier * hashcode + baseHashCode;
        // 将object添加到updateList集合
        updateList.add(object);
      }
    
      public int getUpdateCount() {
        return updateList.size();
      }
    
      public void updateAll(Object[] objects) {
        for (Object o : objects) {
          update(o);
        }
      }
    
      /**
       * CacheKey重写了 equals() 和 hashCode()方法,这两个方法使用上面介绍
       * 的 count、checksum、hashcode、updateList 比较两个 CacheKey对象 是否相同
       */
      @Override
      public boolean equals(Object object) {
        // 如果为同一对象,直接返回 true
        if (this == object) {
          return true;
        }
        // 如果 object 都不是 CacheKey类型,直接返回 false
        if (!(object instanceof CacheKey)) {
          return false;
        }
    
        // 类型转换一下
        final CacheKey cacheKey = (CacheKey) object;
    
        // 依次比较 hashcode、checksum、count,如果不等,直接返回 false
        if (hashcode != cacheKey.hashcode) {
          return false;
        }
        if (checksum != cacheKey.checksum) {
          return false;
        }
        if (count != cacheKey.count) {
          return false;
        }
    
        // 比较 updateList 中的元素是否相同,不同直接返回 false
        for (int i = 0; i < updateList.size(); i++) {
          Object thisObject = updateList.get(i);
          Object thatObject = cacheKey.updateList.get(i);
          if (!ArrayUtil.equals(thisObject, thatObject)) {
            return false;
          }
        }
        return true;
      }
    
      @Override
      public int hashCode() {
        return hashcode;
      }
    
      @Override
      public String toString() {
        StringJoiner returnValue = new StringJoiner(":");
        returnValue.add(String.valueOf(hashcode));
        returnValue.add(String.valueOf(checksum));
        updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);
        return returnValue.toString();
      }
    
      @Override
      public CacheKey clone() throws CloneNotSupportedException {
        CacheKey clonedCacheKey = (CacheKey) super.clone();
        clonedCacheKey.updateList = new ArrayList<>(updateList);
        return clonedCacheKey;
      }
    
    }

    MyBatis的缓存模块, Cache接口以及多个实现类的具体实现,是 Mybatis 中一级缓存和二级缓存的基础。

    展开全文
  • 1.一级缓存&二级缓存缓存存在的目的是为了缓解cpu高速运算速率与读取内存低速率之间的矛盾,提高cpu读取的命中率,cpu读取数据的时候,首先在缓存中查找,然后再去内存中查找,因为读取缓存速度比读取内存速度快得多...
  • CPU中的一级缓存,二级缓存,三级缓存

    万次阅读 多人点赞 2018-06-14 13:49:56
    CPU中的一级缓存,二级缓存,三级缓存 缓存又叫高速缓冲存储器,其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。 缓存的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当...
  • 一级缓存、二级缓存、三级缓存

    千次阅读 2019-10-02 18:39:59
    大家都知道CPU缓存很重要,但对于缓存的具体细分却知之甚少,本文只要是关于CPU缓存的介绍,并着重描述了一级缓存、二级缓存、三级缓存区别方法。 CPU缓存 CPU缓存(Cache Memory)是位于CPU与内存之间的临时...
  • Hibernate缓存策略(一级缓存、二级缓存)

    万次阅读 多人点赞 2018-05-16 16:38:52
    Hibernate是个持久化框架,经常需要... 缓存就是数据库数据在内存中的临时容器,包括数据库数据在内存中的临时拷贝,它位于数据库与数据库访问中间层,ORM在查询数据时,首先会根据自身的缓存管理策略,在缓存中...
  • 缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。 缓存的介质一般是内存,所以读写速度很快。但如果缓存中存放的数据量非常大时,也...
  • 【CPU缓存】一级二级三级缓存

    千次阅读 2019-10-16 16:09:46
    CPU缓存   CPU缓存(Cache Memory)是位于 CPU 与内存之间的临时存储器,它的容量比内存小的多,但是交换速度却快得多...在缓存中的数据是内存中的小部分,但这小部分是短时间CPU即将访问的(?如何判断),...
  • 一、前言MyBatis将数据缓存设计成两级结构,分为一级缓存、二级缓存:一级缓存是Session会话级别的缓存,位于表示一次数据库会话的SqlSession对象之中,又被称之为本地缓存。一级缓存是MyBatis内部实现的一个特性,...
  • L1 Cache(一级缓存)

    千次阅读 2019-05-24 16:31:08
    CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写...
  • 一级缓存和二级缓存区别

    千次阅读 2016-08-16 21:06:14
     如果短时间这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据。  它是内置的事务范围的缓存,不能被卸载。 二级缓存:  就是SessionFact
  • CPU一级缓存、二级缓存、缓存命中率 CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的...
  • 寄存器个数,现在Intel的最新一代CPU里大概有上百个寄存器,扣除重复使用的相同空间的寄存器,寄存器的大小大概是2KB多,具体的寄存器如下图: ...一级缓存(Level 1 Cache)简称L1 Cache,位于CPU内核的旁边...
  • 一二三级缓存的区分

    千次阅读 2019-03-25 14:59:15
    一级缓存和二级缓存的区别: 主要的不同是它们的作用范围不同。 一级缓存是session级别的。 也就是只有在同一个session里缓存才起作用,当这个session关闭后这个缓存就不存在了。 而二级缓存是sessionFactory...
  • 今天来聊个面试中经常会被问到的问题,咱们一起必须把这个问题搞懂。问题:spring 中为什么需要用三级缓存来解决这个问题?用二级缓存可以么?我先给出答案:不可用。这里先声明下:本文未指明...
  • 初步理解三级缓存Cache

    千次阅读 2021-03-30 20:42:38
    这个问题一直困扰我,直到有天不知道在哪听到还是看到句话(大概是这个意思):“如果把cache优化的差不多了,那么性能就几乎没有什么可优化的空间了。”听到这句话后,文化程度不高的我立马拍手叫绝,惊呼WOCAO...
  • Hibernate一级缓存——Session

    千次阅读 2016-12-03 12:32:28
     Session具有缓存位于缓存中的对象称为持久化对象,它和数据库中的相关记录对应。Session能够在某些时间点按照缓存中对象的变化来执行相关的SQL语句,来同步更新数据库。站在持久化的角度,Hibernate把对象分
  • 本节目录1、从 SQL 查询流程看一二级缓存1.1 创建Executor1.2 CachingExecutor#query1.2.1 二级缓存1.2.1.1 MappedStatement#cache属性创建机制1.2.1.2 cache标签解析1.2.1.3 cacheRef1.2.2 一级缓存2、从SQL更新...
  •  缓存:一般人的理解是在内存中的块空间,可以将二级缓存配置到硬盘。用白话来说,就是个存储数据的容器。我们关注的是,哪些数据需要被放入二级缓存。  作用:降低应用程序直接读写数据库的频率,从而提高...
  • Hibernate一级缓存与二级缓存的区别

    千次阅读 2018-07-17 22:40:36
    一级缓存:  就是Session级别的缓存。一个Session做了一个查询... 如果短时间这个session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据。  它是...
  • hibernate二级缓存作用、配置

    千次阅读 2017-08-09 17:45:02
    缓存:缓存是什么,解决什么问题? ...1.事务范围(单Session即一级缓存) 事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存的数据通常采用相互关联的对象形式.缓存的生命周期依
  • Session的一级缓存

    千次阅读 2015-12-14 12:36:54
    Session具有一个缓存,是一块内存空间,在这个内存空间... Session的缓存是内置的,不能被卸除的,也被称为Hibernate的第一级缓存。在正常情况下一级缓存是由Hibernate自动维护的,无需人工干预。 (一)
  • CPU 与 Memory 内存之间的三级缓存的实现原理 1.1 cache 存在的原理   引入 Cache 的理论基础是程序局部性原理,包括时间局部性和空间局部性。时间局部性原理即最近被CPU访问的数据,短期CPU 还要访问(时间...
  • hibernate一级缓存和二级缓存的区别

    千次阅读 2019-02-01 11:21:39
    hibernate一级缓存和二级缓存的区别
  • 在介绍hibernate的缓存机制前,我们先了解一下什么缓存:  缓存(Cache): 计算机领域非常通用的概念。里面放东西,说白了缓存就是个集合。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其...
  • hibernate一级缓存和二级缓存的区别: 主要的不同是它们的作用范围不同。 一级缓存是session级别的。 也就是只有在同一个session里缓存才起作用,当这个session关闭后这个缓存就不存在了。 而二级缓存是...
  • PrimoCache支持任意配置单双级缓存,即可以仅使用一级缓存或仅使用二级缓存,也可以同时使用一级缓存和二级缓存。 由于二级缓存存储设备具有数据永续性的特性,二级缓存中的缓存内容在计算机关机后不会丢失。因此当...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 91,372
精华内容 36,548
关键字:

一级缓存位于什么内