Mybatis源码学习笔记(五)之Mybatis框架缓存机制原理解析

news/2024/4/27 0:37:11/文章来源:https://blog.csdn.net/qq_41865652/article/details/129174882

1 Mybatis框架的缓存模块

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。Mybatis框架中的缓存分为一级缓存和二级缓存,三级缓存基本都要借助自定义缓存或第三方服务来进行实现。但本质上是一样的,都是借助Cache接口实现的。缓存模块在Mybatis的源码结构中是在 org.apache.ibatis.cache包下面存放着的, 如下图:
在这里插入图片描述

2 Cache接口

Cache接口是缓存模块中最核心的接口, Mybatis框架中的实现类如下图所示,起始核心的实现类就只有一个, 就是
PerpetualCache类。
在这里插入图片描述
其他的实现类都是通过装饰者模式对PerpetualCache类的功能扩展和增强, 有兴趣可以自行查看源码。

2.1 PerpetualCache实现类的基本实现

核心方法都比较简单,都是一些很容易理解的方法,说白了就是对Map集合基本操作的封装。
在这里插入图片描述

2.2 Mybatis中缓存机制和属性

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML
映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

2.3 Mybatis中缓存属性说明

这些属性可以通过 cache 元素的属性来修改。比如:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

翻译一下上面这行代码:

    1. eviction属性代表缓存清除策略

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

Mybatis默认清除策略是 LRU

    1. flushInterval属性代表缓存刷新时间间隔

刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

    1. size属性代表缓存对象数量

引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024

    1. readOnly属性代表缓存对象是否只读

(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。
因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。
速度上会慢一些,但是更安全,因此默认值是 false

注意:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的
insert/delete/update 语句时,缓存会获得更新。

3 Cache装饰器

3.1 为什么Myabtis框架要提供那么多的缓存装饰器

首先通过本文第2小节我们已经知道了Mybatis框架提供了一个Cache接口,同时提供了一个基本的缓存实现PerpetualCache,基本的缓存功能都已经提供了,但是这里有没有什么问题?

这里直接抛出几个问题, 大家思考一下:

  • 问题1: 你说缓存清除策略有好多种,如果我现在不想用LRU策略,我想改成其他的策略,你怎么让我通过简单的配置就能实现?
  • 问题2: 高并发的情况下, 怎么保证缓存操作的安全?
  • 问题3: 在开启事务的情况下,因为事务中可能执行的一组SQL指令,每个指令可能都会涉及到缓存操作,怎么保证缓存的批量操作及安全性?
  • 问题4: 缓存毕竟是在堆中开辟的内存空间,会消耗内存, 当内存占用超过一定比例我想把数据持久化到磁盘,怎么操作?
  • 问题5:在缓存的操作时我希望通过日志文件或者控制台打印的方式看到缓存操作的结果,比如缓存命中的结果,存入缓存的结果等等信息,该怎么实现?

这些问题大家想想,如果只依赖基础的PerpetualCache实现类, 能帮我们实现么, 肯定是不能的,需要我们自己去实现么? 肯定也是不需要的, Mybatis框架的设计者就这些问题结合设计模式为我们提供了现成的解决方案,就是一下这些缓存装饰器, 我们可以通过多个组合使用来实现我们在实际业务中的一些特殊的需求。

缓存实现类实现类说明装饰缓存属性条件主要功能和作用
基本缓存缓存基本实现类默认是PerpetualCache,也可以自定义比如RedisCache、EhCache等,具备基本功能的缓存类
LruCacheLRU策略的缓存eviction=“LRU”(默认)当缓存到达上限时候,删除最近最少使用的缓存(Least Recently Use)
FifoCacheFIFO策略的缓存eviction=“FIFO”当缓存到达上限时候,删除最先入队的缓存
SoftCacheWeakCacheeviction="SOFT"eviction=“WEAK”带清理策略的缓存通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存,基于SoftReference和WeakReference
LoggingCache带日志功能的缓存Base比如:输出缓存命中率
SynchronizedCache同步缓存Base基于synchronized关键字实现,解决并发问题
BlockingCache阻塞缓存blocking=true通过在get/put方式中加锁,保证只有一个线程操作缓存,基于Java重入锁实现
SerializedCachereadOnly=false(默认)支持序列化的缓存将对象序列化以后存到缓存中,取出时反序列化
ScheduledCache定时调度的缓存flushInterval不为空在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间(默认是一小时),如果是则清空缓存–即每隔一段时间清空一次缓存
TransactionalCache事务缓存在TransactionalCacheManager中用Map维护对应关系在二级缓存中使用,可一次存入多个缓存,移除多个缓存

下面通过SynchronizedCache实现类来进行说明,

3.2 SynchronizedCache

/**    Copyright 2009-2022 the original author or authors.**    Licensed under the Apache License, Version 2.0 (the "License");*    you may not use this file except in compliance with the License.*    You may obtain a copy of the License at**       http://www.apache.org/licenses/LICENSE-2.0**    Unless required by applicable law or agreed to in writing, software*    distributed under the License is distributed on an "AS IS" BASIS,*    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.*    See the License for the specific language governing permissions and*    limitations under the License.*/
package org.apache.ibatis.cache.decorators;import org.apache.ibatis.cache.Cache;/*** @author Clinton Begin* 这个实现类的作用就是处理在并发缓存操作的安全问题, * 所有的方法都加了synchronized关键字来保证线程安全*/
public class SynchronizedCache implements Cache {private final Cache delegate;public SynchronizedCache(Cache delegate) {this.delegate = delegate;}@Overridepublic String getId() {return delegate.getId();}@Overridepublic synchronized int getSize() {return delegate.getSize();}@Overridepublic synchronized void putObject(Object key, Object object) {delegate.putObject(key, object);}@Overridepublic synchronized Object getObject(Object key) {return delegate.getObject(key);}@Overridepublic synchronized Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic synchronized void clear() {delegate.clear();}@Overridepublic int hashCode() {return delegate.hashCode();}@Overridepublic boolean equals(Object obj) {return delegate.equals(obj);}}

通过源码我们能够发现,SynchronizedCache本质上就是在我们操作缓存数据的实际调用方法上加了synchronized 同步锁保证了线程安全。其他具体实现类,大家可以自行查阅源码。

4 Mybatis框架中缓存应用

4.1 框架启动缓存初始化

4.1.1 缓存配置解析的几个相关点介绍

配置文件中缓存相关的配置参数解析
在这里插入图片描述
Myabtis框架中一级缓存和二级缓存默认是开启的
在这里插入图片描述
缓存默认的作用域是Session
在这里插入图片描述
Configuration初始化的时候会为我们的各种Cache实现类完成别名注册
在这里插入图片描述

4.1.2 缓存对象的创建及初始化过程

解析全局配置文件mybatis-config.xml时会解析cacheEnabled属性并赋值给Configuration类的cacheEnabled属性, 默认值为true
在这里插入图片描述
如果你启动了二级缓存的配置, 那么在解析*Mapper.xml配置文件时,也会解析响应的cache属性,创建Cache对象并设置到Configuration类的caches属性中区,源码中具体实现见下图:
在这里插入图片描述
上图中的最后一行builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);调用了MapperBuilderAssistant类中的useNewCache()方法, 具体代码实现细节见下图, 也很简单:
在这里插入图片描述

4.1.3 缓存初始化流程图

在这里插入图片描述

4.2 一级缓存

首先需要明白的是我们需要缓存的对象是什么? 基本上就可以猜测出来我们需要在框架的那个具体环节进行处理。

通常我们呢需要缓存的就是我们从数据库中匹配出来的数据,所以这个缓存的操作一定是发生在SQL语句执行阶段的, 在Mybatis框架中专门负责语句执行的就是Executor, 所有我们呢只需要在Executor的实现类中查找关于缓存的处理逻辑代码实现即可。

一级缓存也叫本地缓存(Local Cache)。

MyBatis的一级缓存是在会话(SqlSession)层面进行缓存的。

MyBatis的一级缓存是默认开启的,不需要做任何的配置。

如果我们需要关闭一级缓存, 只需要修改Configuration中localCacheScope属性的取值为STATEMENT即可。

4.2.1 一级缓存什么时候创建

在我们的Executor对象创建的时候, 我们的一级缓存localCache对象就已经创建了,Executor对象又是什么时候创建的,是在SqlSession对象创建的时候完成的创建。验证流程如下图:
在这里插入图片描述
一级缓存对象是在BaseExecutor的构造器执行的时候进行创建的, BaseExecutor的构造器是在new SimpleExecutor()的时候被调用的,Configuration类的newExecutor方法中创建了SimpleExecutor对象,
而Configuration类的newExecutor方法又在factory.openSession()方法执行时被调用。
所以结论就是在我们创建sqlSession对象完成的时候, 一级缓存对象就已经准备就绪了。

4.2.2 一级缓存如何关闭

Mybatis框架中提供了关闭一级缓存的示例代码,在BaseExecutor类的query()方法中,如下:
在这里插入图片描述

4.2.3 一级缓存的具体实现

一级缓存的实现是在BaseExecutor执行器实现类中实现的,具体代码如下:

 @Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameter);// 创建缓存KeyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);// 执行查询方法return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}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.");}// flushCache="true"时,即使是查询操作也要先清空一级缓存if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {// 防止递归查询重复处理缓存queryStack++;// 查询一级缓存list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;// 一级缓存是否命中if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {// 如果没有命中,从数据库中查询list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}// issue #601deferredLoads.clear();// 如果当前本地缓存的作用域设置为STATEMENT,则将一级缓存关闭(清空相当于关闭操作)if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {// issue #482clearLocalCache();}}return list;}

4.2.3 一级缓存的实现流程图

在这里插入图片描述

4.2.4 一级缓存的测试验证

验证思路, 根据上面的流程图设计, 如果一级缓存命令, 就不会执行数据库查询操作, 那么查询语句在控制台肯定就只会输出一次, 同时两次查询都可以查询出结果。按照这个思路我们写一个单元测试来验证一下:

1)验证一同一个SqlSession会话中多次调用同一个查询方法,验证一级缓存是否生效

验证代码

public static void main(String[] args) {SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {SqlSessionFactory factory = factoryBuilder.build(ins);SqlSession sqlSession = factory.openSession(true);LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);System.out.println("------------------------------------------");libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);} catch (IOException e) {e.printStackTrace();}}

验证结果
在这里插入图片描述
验证结论:一级缓存只在同一sqlSession会话内的相同查询(方法相同、输入参数相同)才生效。

2)验证二不同SqlSession会话中调用同一个查询方法,验证一级缓存是否生效

验证代码

SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {SqlSessionFactory factory = factoryBuilder.build(ins);SqlSession sqlSession = factory.openSession(true);SqlSession sqlSession1 = factory.openSession(true);LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);System.out.println("------------------------------------------");libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);} catch (IOException e) {e.printStackTrace();}

验证结果
在这里插入图片描述
验证结论:一级缓存的作用范围是"Session",只在Session会话级别才生效

4.3 二级缓存

二级缓存是用来解决一级缓存不能跨会话(SqlSession)共享的问题的,范围是Mapper级别的,可以被多个SqlSession共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

Mybatis框架中设计的二级缓存默认也是开启的, 但是我们在应用层调用时, 需要进行一些配置才可以生效。

同样我们通过缓存的作用范围再来反推一下, 二级缓存应该是发生在什么时候, 二级缓存的默认的作用范围是在Statement, 必然也是在Executor执行的时候才会进行缓存操作。

同时应为全局的cacheEnabled属性默认为true, 所以在创建Executor对象的时候会被CachingExecutor装饰, 那么二级缓存相关的操作大概率是在CachingExecutor类中实现的,下面我们到源码中验证一下

4.3.1 二级缓存生效配置

第一步:在全局配置文件mybatis-config.xml文件的settings标签对中开启全局配置二级缓存开启的配置

    <settings><!--  二级缓存开启, 默认为true --><setting name="cacheEnabled" value="true" /></settings>

在这里插入图片描述
第二步:在mapper映射配置文件**Mapper.xml文件的增加如下配置:

<mapper namespace="com.kkarma.mapper.LibBookMapper"><cache eviction="LRU" flushInterval="60000" size="512" readOnly="true" />	
</mapper>

4.3.2 二级缓存源码解析

4.3.2.1 二级缓存对象如何存放

Myabtis框架中对于缓存的存储都是在Configuration类中的caches属性中存储的。通过CacheKey进行一级和二级缓存的区分
在这里插入图片描述

4.3.2.2 二级缓存对象初始化过程

*Mapper.xml文件中的解析顺序如下:
在这里插入图片描述
二级缓存对象是在**Mapper.xml文件解析的时候进行初始化, 因为如果开启二级缓存,*Mapper.xml文件中势必会配置cache标签,会先解析cache标签为当前mapper.xml文件的命名空间创建一个Cache对象。
在这里插入图片描述
在这里插入图片描述
在创建mappedStatement对象时, 会将单前命名空间中解析出来的二级缓存对象设置到当前mappedStatement对象中去。
在这里插入图片描述
在这里插入图片描述

4.3.2.3 二级缓存的具体实现

二级缓存的操作是在CachingExecutor中完成的,

  @Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);// 创建缓存KEYCacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// 执行查询return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {// 获取当前mapper接口对应的缓存对象, 这里就是二级缓存// 二级缓存作为MappedStatement对象的一个属性,之前我们已经解析过, mapper映射文件解析之后会生成对应的MappedStatement对象// 所以二级缓存肯定是在MappedStatement创建的时候进行的初始化,验证一下Cache cache = ms.getCache();if (cache != null) {// 如果当前方法需要在执行之前刷新缓存, 这里就执行刷新缓存操作flushCacheIfRequired(ms);// 如果当前mappedStatement对象启用缓存并且未设置resultHandlerif (ms.isUseCache() && resultHandler == null) {// 这里是排除存储过程语句执行的干扰ensureNoOutParams(ms, boundSql);@SuppressWarnings("unchecked")// 从二级缓存中查询结果List<E> list = (List<E>) tcm.getObject(cache, key);// 如果缓存没命中,从数据库中进行数据查询if (list == null) {list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 将查询结果设置到二级缓存中tcm.putObject(cache, key, list); // issue #578 and #116}// 返回查询结果为调用者return list;}}// 如果当前mappedStatement对象未启用缓存或设置了resultHandlerreturn delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

4.3.3 如何关闭二级缓存

在这里插入图片描述
**加粗样式**
在这里插入图片描述
那么如果关闭二级缓存呢?
在这里插入图片描述

4.3.3 二级缓存的测试验证

2)验证二不同SqlSession会话中调用同一个查询方法,验证一级缓存是否生效

验证代码

public static void main(String[] args) {SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {SqlSessionFactory factory = factoryBuilder.build(ins);SqlSession sqlSession = factory.openSession(true);SqlSession sqlSession1 = factory.openSession(true);LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);// sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据// 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中sqlSession.commit();System.out.println("------------------------------------------");libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);sqlSession.close();sqlSession1.close();} catch (IOException e) {e.printStackTrace();}}

验证结果
在这里插入图片描述

验证结论:二级缓存的作用范围是"statement",不同的sqlSession会话针对与同一个mapper命名空间下的方法查询都会走二级缓存。

注意事项
在事务提交之前,并不会真正存储到二级缓存,而是先存储到一个临时属性,等事务提交之后才会真正存储到二级缓存。
这么做的目的就是防止脏读。
因为假如你在一个事务中修改了数据,然后去查询,这时候直接缓存了,那么假如事务回滚了呢?所以这里会先临时存储一下。
在这里插入图片描述
二级缓存的操作最终都是借助TransactionalCache装饰器来实现的。因为二级缓存需要开启事务。
具体代码请自行查看TransactionalCache类的实现

4.4 三级缓存

4.4.1 三级缓存概述

三级缓存一般都是自定义缓存。分布式缓存框架:我们系统为了提高系统并发和性能,一般对系统进行分布式部署(集群部署方式)不适用分布缓存, 缓存的数据在各个服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理.ehcache,redis ,memcache,caffeine缓存框架,这里演示caffeine和redis缓存框架来进行实现:

4.4.2 借助mybatis-caffeine实现三级缓存

这里使用的是mybatis-encache框架来实现:

项目地址: https://github.com/mybatis/caffeine-cache

文档地址:https://mybatis.org/caffeine-cache/

这个代码本质上就是通过拓展实现Mybatis框架的Cahce接口来实现的,有兴趣可以自行研究,代码实现也很简单,很容易看。

4.4.2.1 mybatis-caffeine三级缓存配置

第一步:在我们的pom文件中引入依赖

 <dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-caffeine</artifactId><version>1.0.0</version></dependency>

第二步:开启二级缓存
在这里插入图片描述
在这里插入图片描述

第三步:将*Mapper.xml文件中的二级缓存类型替换成CaffeineCache

<mapper namespace="org.kkarma.mapper.LibBookMapper"><cache type="org.mybatis.caches.redis.CaffeineCache" />
</mapper>
4.4.2.2 三级缓存测试验证

验证代码

public static void main(String[] args) {SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {SqlSessionFactory factory = factoryBuilder.build(ins);SqlSession sqlSession = factory.openSession(true);SqlSession sqlSession1 = factory.openSession(true);LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);// sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据// 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中sqlSession.commit();System.out.println("------------------------------------------");libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);sqlSession.close();sqlSession1.close();} catch (IOException e) {e.printStackTrace();}}

在这里插入图片描述

4.4.3 借助mybatis-redis实现三级缓存

这里使用的是mybatis-redis框架来实现:

项目地址: https://github.com/mybatis/redis-cache

文档地址:http://mybatis.org/redis-cache/index.html

这个代码本质上就是通过拓展实现Mybatis框架的Cahce接口来实现的,有兴趣可以自行研究,代码实现也很简单,很容易看。

4.4.3.1 mybatis-redis三级缓存配置

第一步:在我们的pom文件中引入依赖

 <dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version></dependency>

第二步:开启二级缓存
在这里插入图片描述
在这里插入图片描述
第三步:将*Mapper.xml文件中的二级缓存l类型替换成RedisCache

<mapper namespace="org.kkarma.mapper.LibBookMapper"><cache type="org.mybatis.caches.redis.RedisCache" />
</mapper>

第四步:在resoure目录下增加redis连接的相关配置文件redis.properties,文件内容如下:

host=***.***.***.***
port=****
password=********
databse=*

在这里插入图片描述

4.4.3.2 三级缓存测试验证

验证代码

public static void main(String[] args) {SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();try (InputStream ins = Resources.getResourceAsStream("mybatis-config.xml")) {SqlSessionFactory factory = factoryBuilder.build(ins);SqlSession sqlSession = factory.openSession(true);SqlSession sqlSession1 = factory.openSession(true);LibBookMapper mapper = sqlSession.getMapper(LibBookMapper.class);LibBookMapper mapper1 = sqlSession1.getMapper(LibBookMapper.class);List<LibBook> libBooks = mapper.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);// sqlSession在执行完查询操作之后, 必须提交之后,缓存才会生效, 其他sqlSession对象次才能查询到缓存的数据// 如果这里不进行提交操作, 那么sqlSession1执行相同方法时也会查询数据库, 缓存不会命中sqlSession.commit();System.out.println("------------------------------------------");libBooks = mapper1.selectBooksByCondition("T311.5/3-1", null, null, null);libBooks.forEach(System.out::println);sqlSession.close();sqlSession1.close();} catch (IOException e) {e.printStackTrace();}}

查询缓存中是否存在数据
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_74752.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python学习笔记】第十九节 Python 面向对象(一)

在现实世界中&#xff0c;随处可见的一种事物就是对象&#xff0c;对象是事物存在的实体&#xff0c;如学生、汽车等。人类解决问题的方式总是将复杂的事物简单化&#xff0c;于是就会思考这些对象都是由哪些部分组成的。通常都会将对象划分为两个部分&#xff0c;即静态部分与…

SSL证书对虚拟主机的用处有哪些?

虚拟主机是指在同一台服务器上&#xff0c;通过不同的域名或IP地址为多个网站提供服务的一种网络主机。而SSL证书则是一种数字证书&#xff0c;它用于加密网站与用户之间的通信&#xff0c;确保数据传输的安全性和完整性。在虚拟主机上&#xff0c;SSL证书有以下几个用处&#…

HiveSql一天一个小技巧:如何巧用分布函数percent_rank()求去掉最大最小值的平均薪水问题

0 问题描述参考链接(3条消息) HiveSql面试题12--如何分析去掉最大最小值的平均薪水&#xff08;字节跳动&#xff09;_莫叫石榴姐的博客-CSDN博客文中已经给出了三种解法&#xff0c;这里我们借助于此题&#xff0c;来研究如何用percent_rank()函数求解&#xff0c;简化解题思路…

深入理解C#的协变和逆变及其限制原因

阅读本文需要的一些前置知识&#xff1a; C#基本语法、C#的泛型使用、C#的运行过程 由于协变和逆变存在一些细节&#xff0c;在阅读时请注意“接口”和“类型”的差异&#xff0c;此外&#xff0c;文中有可能在不同的语境中将“结构体”和“值类型”混用&#xff0c;但表达的同…

深入浅出1588v2(PTP)里的时间同步原理

1.时间同步1.1 单步同步(OneStep)单步同步最为简单&#xff0c;master向slave发送一个sync的同步包&#xff0c;同步包里带有这条信息发送时master的当前时间t1&#xff0c;假如这条信息从master传输到slave需要的传输时间是D&#xff0c;那么slave收到信息时&#xff0c;maste…

BIM小技巧丨关于如何在Revit明细表中显示门窗面积

在明细表中显示门窗面积(以门明细表为例)在新建一个门明细表后&#xff0c;可以发现在Revit中不能直接使用明细表统计门窗面积。 这时&#xff0c;可以通过使用添加“计算值”的方式来处理&#xff0c;得到如下图所示&#xff0c;两种不同的面积统计结果&#xff1a; 除此之外&…

前端基础之CSS扫盲

文章目录一. CSS基本规范1. 基本语法格式2. 在HTML引入CSS3. 选择器分类二. CSS常用属性1. 文本属性2. 文本格式3. 背景属性4. 圆角矩形和圆5. 元素的显示模式6. CSS盒子模型7. 弹性布局光使用HTML来写一个前端页面的话其实只是写了一个大体的框架, 整体的页面并不工整美观, 而…

ledcode【用队列实现栈】

目录 题目描述&#xff1a; 解析题目 代码解析 1.封装一个队列 1.2封装带两个队列的结构体 1.3封装指向队列的结构体 1.4入栈函数实现 1.5出栈函数实现 1.6取栈顶数据 1.7判空函数实现 题目描述&#xff1a; 解析题目 这个题我是用c语言写的&#xff0c;所以队列的pu…

JavaSE-3 Java运行原理

一、Java的运行过程 &#x1f34e;Java程序运行时,必须经过编译和运行两个步骤。 首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件。然后Java虚拟机将字节码文件进行解释执行,并将结果显示出来。具体过程如下图所示。 &#x1f349;Java程序的运行过…

【Python数据挖掘入门】2.2文本分析-中文分词(jieba库cut方法/自定义词典load_userdict/语料库分词)

中文分词就是将一个汉字序列切分成一个一个单独的词。例如&#xff1a; 另外还有停用词的概念&#xff0c;停用词是指在数据处理时&#xff0c;需要过滤掉的某些字或词。 一、jieba库 安装过程见&#xff1a;https://blog.csdn.net/momomuabc/article/details/128198306 ji…

数字IC手撕代码--小米科技(除法器设计)

前言&#xff1a; 本专栏旨在记录高频笔面试手撕代码题&#xff0c;以备数字前端秋招&#xff0c;本专栏所有文章提供原理分析、代码及波形&#xff0c;所有代码均经过本人验证。目录如下&#xff1a;1.数字IC手撕代码-分频器&#xff08;任意偶数分频&#xff09;2.数字IC手撕…

原始GAN-pytorch-生成MNIST数据集(代码)

文章目录原始GAN生成MNIST数据集1. Data loading and preparing2. Dataset and Model parameter3. Result save path4. Model define6. Training7. predict原始GAN生成MNIST数据集 原理很简单&#xff0c;可以参考原理部分原始GAN-pytorch-生成MNIST数据集&#xff08;原理&am…

记一次线上es慢查询导致的服务不可用

现象 某日线上业务同学反馈订单列表查询页面一直loding&#xff0c;然后提示请求超时&#xff0c;几分钟之后恢复正常 接到报障之后&#xff0c;马上根据接口URL&#xff0c;定位到了请求链路&#xff0c;发现是es查询超时&#xff0c;这里我们的业务订单表数据是由几百万的&a…

如何基于MLServer构建Python机器学习服务

文章目录前言一、数据集二、训练 Scikit-learn 模型三、基于MLSever构建Scikit-learn服务四、测试模型五、训练 XGBoost 模型六、服务多个模型七、测试多个模型的准确性总结参考前言 在过去我们训练模型&#xff0c;往往通过编写flask代码或者容器化我们的模型并在docker中运行…

Python学习笔记202302

1、numpy.empty 作用&#xff1a;根据给定的维度和数值类型返回一个新的数组&#xff0c;其元素不进行初始化。 用法&#xff1a;numpy.empty(shape, dtypefloat, order‘C’) 2、logging.debug 作用&#xff1a;Python 的日志记录工具&#xff0c;这个模块为应用与库实现了灵…

C# Sqlite数据库加密

sqlite官方的数据库加密是收费的&#xff0c;而且比较贵。 幸亏微软提供了一种免费的方法。 1 sqlite加密demo 这里我做了一个小的demo演示如下&#xff1a; 在界面中拖入数据库名、密码、以及保存的路径 比如我选择保存路径桌面的sqlite目录&#xff0c;数据库名guigutool…

Verilog 学习第五节(串口接收部分)

小梅哥串口部分学习part2 串口通信接收原理串口通信接收程序设计与调试巧用位操作优化串口接收逻辑设计串口接收模块的项目应用案例串口通信接收原理 在采样的时候没有必要一直判断一个clk内全部都是高/低电平&#xff0c;如果采用直接对中间点进行判断的话&#xff0c;很有可能…

Linux 红帽9.0 本地源 与 网络源 搭建

本次我们使用的是 redhat 9.0 版本&#xff0c;是redhat 的最新版本&#xff0c;我们一起来对其进行 本地仓库 和 网络仓库的搭建部署~&#xff01;&#xff01;关于 本地仓库&#xff08; 本地源 &#xff09;&#xff0c;和 网络仓库 &#xff08; 网络源 &#xff09;&#…

ESP32蓝牙配网

注意********menuconfig 配置&#xff08;必须打开蓝牙我这是C2所以使用NimBLE &#xff09;可以直接从demo的配置文件拷贝 Component config ---> Bluetooth ---> NimBLE - BLE only Component config ---> Bluetooth ---> NimBLE Options ---> Enable blufi…

计算结构体大小

计算结构体大小 目录计算结构体大小一. 结构体内存对齐1. 简介2. 嵌套结构体二. offsetof三. 内存对齐的意义四. 修改默认对齐数一. 结构体内存对齐 以字节&#xff08;bety&#xff09;为单位 1. 简介 对于结构体成员在内存里的存储&#xff0c;存在结构体的对齐规则&#…