当前位置: 首页 > news >正文

Mybatis07-缓存

一、缓存机制的原理

计算机每次从mysql中执行sql语句,都是内存与硬盘的通信,对计算机来说,影响效率。

因此使用缓存机制。

 

1-1、MyBatis 的缓存机制:

执行 DQL(select 语句)的时候,将查询结果放到缓存当中(内存当中),如果下一次还是执行完全相同的 DQL(select 语句)语句,直接从缓存中拿数据,不再查数据库了。不再去硬盘上找数据了。

示例:

第一次执行这个 SQL:

select * from t_car where id=1;

第二次还是执行这个 SQL,完全一样:

select * from t_car where id=1;

此时,从缓存中获取,不再查数据库了。

当两条sql语句之间,对数据库做了任何修改操作,缓存将从内存中清除。

目的:提高执行效率。

缓存机制:使用减少 IO 的方式来提高效率。

IO:读文件和写文件。

缓存通常是我们程序开发中优化程序的重要手段:

  • 字符串常量池
  • 整数型常量池
  • 线程池
  • 连接池
  • ……

【小结】:

        缓存(cache)就是内存,提前把数据放到内存中,下一次用的时候,直接从缓存中拿,效率高!

二、几种常见的缓存/池化技术

这些“池”技术,其实都是 Java 中的 缓存/复用机制,目的是:提升性能、减少资源消耗、避免频繁创建和销毁对象。下面来系统讲解几种常见的缓存/池化技术:


2-1、字符串常量池(String Constant Pool)

1、原理:

Java 中字符串是不可变的(final,所以 JVM 会把相同的字符串常量只保留一份副本,存放在一个称为 字符串常量池 的内存区域。

String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
  • "hello" 是一个字符串字面量,保存在常量池中

  • s1 和 s2 都指向同一个地址

2、注意:

String s3 = new String("hello");
System.out.println(s1 == s3); // false(堆 vs 常量池)

如果你想把 new 出来的字符串放入常量池:

String interned = s3.intern();
System.out.println(s1 == interned); // true

 

2-2、整数型常量池(Integer Cache)

1、原理:

Java 对于包装类型 Integer,有一个缓存区[-128, 127]),当你使用 valueOf() 方法创建时,会从缓存中取对象而不是创建新对象。

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(缓存)Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false(未缓存)

2、范围:

JVM 默认缓存范围为 [-128, 127],可以通过启动参数修改

-XX:AutoBoxCacheMax=300

2-3、线程池(Thread Pool)

1、原理:

线程的创建与销毁成本高(涉及操作系统资源),频繁创建新线程会拖慢系统。

所以,线程池把线程复用起来,让多个任务共享固定线程,提高并发效率。

2、常用方式:

        // 创建固定大小为 3 的线程池ExecutorService pool = Executors.newFixedThreadPool(3);// 模拟提交 5 个任务for (int i = 1; i <= 15; i++) {int taskId = i;pool.submit(new Runnable() {public void run() {String threadName = Thread.currentThread().getName();System.out.println("任务 " + taskId + " 开始,线程:" + threadName);try {Thread.sleep(20000); // 模拟任务耗时} catch (InterruptedException e) {e.printStackTrace();}System.out.println("任务 " + taskId + " 结束,线程:" + threadName);}});}// 关闭线程池(注意不是立刻关闭)pool.shutdown();

打印结果: 

 

可以看到线程是复用的。

Executors 提供常见线程池工厂方法:

  • newFixedThreadPool(n)

  • newCachedThreadPool()

  • newSingleThreadExecutor()

  • newScheduledThreadPool(n)


2-4、连接池(Connection Pool)

1、 原理:

数据库连接创建代价高(要连接服务器、授权、建会话),所以使用连接池 复用已建立的连接

2、常见实现:

  • HikariCP(Spring Boot 默认)

  • DBCP

  • C3P0

  • Druid

3、示例(Spring Boot):

spring.datasource.hikari.maximum-pool-size: 10

应用启动后,会提前建立 10 个连接,放入连接池,供业务查询复用。


2-5、对象池(Object Pool)

对于那些频繁使用又比较重量级的对象(如:ByteBuffer, Socket, 数据库连接),也可以池化处理。

Java 标准库没有通用的 ObjectPool,但 Apache Commons Pool 提供支持。

GenericObjectPool<MyReusableObject> pool = new GenericObjectPool<>(new MyObjectFactory());MyReusableObject obj = pool.borrowObject(); // 借
obj.doSomething();                          // 用
pool.returnObject(obj);                     // 还

你需要在项目中引入 Apache Commons Pool 的依赖(如果用 Maven):

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version> <!-- 可根据需要换版本 -->
</dependency>

 


2-6、内存缓存(如 LRU 缓存)

Java 中可以自己实现缓存算法(如 LRU),也可以使用:

  • Guava Cache

  • Caffeine(高性能)

  • Ehcache

  • Redis(分布式)

示例(Caffeine):

Cache<String, Object> cache = Caffeine.newBuilder().maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES).build();

2-7、类加载缓存(ClassLoader)

JVM 对每个类只加载一次,会将 .class 文件缓存到内存中,后续实例化只需创建对象而不重复加载。


2-8、反射缓存(Method/Field 缓存)

使用反射获取字段/方法(如 Class.getDeclaredMethods())是慢操作。JVM 会自动缓存这些反射结构,Spring、MyBatis 等框架也会自己做缓存。


2-9、JVM 运行时常见缓存(底层)

缓存类型说明
字符串常量池复用 String 常量
Integer 缓存避免频繁装箱创建 Integer
Class 常量池常量值如 final 字段、枚举等
方法句柄缓存JVM 优化调用性能
Lambda 表达式缓存编译后只创建一次匿名类对象

2-10、总结对比表

技术名称类型缓存对象控制方式是否可配置
字符串常量池编译期/运行期String自动/intern()
Integer 缓存运行期Integer自动/valueOf()
线程池并发ThreadExecutors
连接池IO资源DB连接DataSource
ObjectPool自定义业务对象Apache Commons
Guava/Caffeine本地缓存任意对象API构建
Redis分布式缓存任意对象客户端控制

三、Mybatis的缓存

mybatis 缓存包括:

  • 一级缓存:将查询到的数据存储到 SqlSession 中。
  • 二级缓存:将查询到的数据存储到 SqlSessionFactory 中。
  • 集成其它第三方的缓存:比如 EhCache【Java 语言开发的】、Memcache【C 语言开发的】等。

SqlSessionFactory是一个数据库一个,SqlSession作用域是当前的sql会话。

缓存只针对DQL语句,也就是说:缓存只针对select语句!

 

3-1、MyBatis 一级缓存

3-1-1、什么是一级缓存?

一级缓存是 MyBatis 的默认缓存机制,作用范围是 一次 SqlSession 内部。简单说:

同一个 SqlSession 中,相同的查询语句和参数,MyBatis 会从缓存中取数据,不会再次访问数据库

一级缓存mybatis默认开启,不需要任何配置!


3-1-2、一级缓存工作流程图

SqlSession├── 查询语句 1(未命中缓存) → 查数据库,缓存结果├── 查询语句 1(再次执行) → 命中缓存,直接返回└── SqlSession.close() → 缓存销毁

3-1-3、一级缓存使用示例

@Test
public void testFirstLevelCache() {SqlSession session = sqlSessionFactory.openSession();UserMapper mapper = session.getMapper(UserMapper.class);// 第一次查询,去数据库User u1 = mapper.selectById(1L);System.out.println("第一次查询:" + u1);// 第二次查询相同 ID,命中缓存User u2 = mapper.selectById(1L);System.out.println("第二次查询:" + u2);System.out.println(u1 == u2); // true(同一个对象)session.close();
}

【注意】:

此时,控制台只执行一条sql select语句! 


 

3-1-4、哪些情况会导致缓存失效?

  1. SqlSession 不是同一个

    每次 openSession() 创建新的 Session,缓存就不同。

    示例:

        @Testpublic void testFirstCache2() throws IOException {SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));SqlSession sqlSession1 = sqlSessionFactory.openSession();CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);Car car1 = mapper1.selectOneById(1L);System.out.println(car1);SqlSession sqlSession2 = sqlSessionFactory.openSession();CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);Car car2 = mapper2.selectOneById(1L);System.out.println(car2);sqlSession1.close();sqlSession2.close();}

    此时,控制台会打印两条sql select语句。

  2. 执行了 update / insert / delete 操作

    写操作会清空缓存(保证数据一致性),修改任意一张表,都会清空缓存!

  3. 手动清空缓存

    session.clearCache();
    
  4. 查询的 SQL 或参数不同


 

3-1-5、一级缓存原理简述

  • MyBatis 内部维护了一个 PerpetualCacheHashMap 实现)

  • 每次查询前会根据 SQL+参数,生成 key,先查缓存

  • 如果命中,直接返回

  • 如果没命中,查数据库并存入缓存

3-2、MyBatis 二级缓存

MyBatis 的二级缓存,这对优化多次查询、减少数据库压力非常重要,尤其是跨 SqlSession 的查询场景


 

3-2-1、什么是二级缓存?

二级缓存是 MyBatis 提供的跨 SqlSession 的缓存机制。

它的作用范围是:Mapper 映射级别(namespace)不同 SqlSession 之间共享缓存数据


3-2-2、一级 vs 二级缓存对比

对比项一级缓存(默认)二级缓存(需开启)
缓存范围单个 SqlSession 内多个 SqlSession 共享
默认状态开启默认关闭
生命周期SqlSession 生命周期应用级、映射器级别
存储结构基于 HashMap(PerpetualCache)可自定义实现
典型用途同一次操作内避免重复查询缓解高频读操作数据库压力

3-2-3、使用二级缓存的三步配置


Step 1:开启全局二级缓存
<settings><setting name="cacheEnabled" value="true"/>
</settings>

【注意】:

是的,<setting name="cacheEnabled" value="true"/> 在 MyBatis 中默认就是开启。 


Step 2:在 Mapper 映射文件中开启 <cache/>

例如:UserMapper.xml

<mapper namespace="com.example.mapper.UserMapper"><cache /><select id="selectById" resultType="User">SELECT * FROM users WHERE id = #{id}</select>
</mapper>
  • 你也可以配置更多参数(见下方扩展)


Step 3:实体类实现 Serializable 接口(因为缓存需要序列化)
public class User implements Serializable {private Long id;private String name;// ...其他字段
}

3-2-4、二级缓存使用示例

示例1:没有使用二级缓存

示例2:数据从二级缓存中获取

sqlSession1关闭,数据保存到二级缓存中,再执行sqlSession2中的select语句,会从二级缓存中获取。

【注意】:

一级缓存的优先级高,先从一级缓存中取数据,若是一级缓存关闭,则从二级中取数据!

 

示例3:跨namespace测试二级缓存

【注意】:

两个mapper不一样,但是执行的select语句和参数都是一样的,但是控制台依旧会执行两条select查询语句,说明二级缓存不能跨namespace!


3-2-5、哪些操作会清空二级缓存?

  • 对该 namespace 进行了 update/insert/delete(增、删、改)

  • 显式调用了 clearCache()

  • 配置 <cache flushInterval="..."/> 自动过期

  • 跨 namespace 无法共享(除非手动自定义)


3-2-6、常见 <cache> 配置项

<cache eviction="LRU"               <!-- 缓存淘汰策略:LRU, FIFO, SOFT, WEAK -->flushInterval="60000"        <!-- 自动刷新间隔:毫秒;刷新后二级缓存失效 -->size="512"                   <!-- 最大缓存对象数量 -->readOnly="false"             <!-- 是否只读(只读更快但不可修改), car1 == car2 -->blocking="true"              <!-- 防止缓存击穿 -->
/>

 

3-2-7、一级缓存 vs 二级缓存(图解理解)

+------------------------+
| SqlSession A           |
|  └── 一级缓存(仅自己用)     |
|                        |
| SqlSession B           |
|  └── 一级缓存(仅自己用)     |
+------------------------+↓(关闭 SqlSession 后)
+------------------------+
|     二级缓存(共享)          |
|     key: SQL + 参数          |
|     value: 查询结果对象      |
+------------------------+

3-3、自定义缓存实现(可选)

MyBatis 允许你自定义二级缓存逻辑(如整合 Redis),也就是集成第三方的缓存组件。

【注意】:

MyBatis的一级缓存是不可替代的!集成第三方的缓存组件,替代的是二级缓存!

MyBatis 如何集成第三方缓存组件,比如 Redis、EhCache、Caffeine 等。这种方式可以将 MyBatis 的二级缓存升级为分布式或高性能缓存,实现更强的可扩展性与性能提升。 

1、示例:集成EhCache 

step1:pom.xml中添加依赖
<!-- MyBatis 对 EhCache 的支持 -->
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version>
</dependency>

step2: 添加 ehcache.xml 配置文件
<ehcache><cache name="com.example.mapper.UserMapper"maxEntriesLocalHeap="1000"timeToLiveSeconds="600"/>
</ehcache>

step3: 在对应的xxxMapper.xml 中配置:

step4: 编写测试类

测试类和测试二级缓存一样,没有变动!

http://www.xdnf.cn/news/1130959.html

相关文章:

  • 【LLM】OpenRouter调用Anthropic Claude上下文缓存处理
  • Jenkins Pipeline 中使用 JsonSlurper 报错:cannot find current thread
  • 55. 跳跃游戏
  • 2025年中国品牌全球化发展分析:中国品牌在社交渠道、电商平台及官网流量方面显著增长
  • 语音增强论文汇总
  • IIS网站间歇性打不开暴力解决方法
  • 【数据结构】栈与链表的区别
  • 【Qt开发】Qt的背景介绍(二)-> 搭建Qt开发环境
  • 如何在硬件中进行有效地调试
  • TCP 三次握手与四次挥手笔记
  • 前端vue3获取excel二进制流在页面展示
  • Promise与Axios:异步编程
  • sqli-labs靶场通关笔记:第23关 注释符过滤
  • React -自定义hooks - 封装双向数据绑定
  • 自动控制原理知识地图:舵轮、路径与导航图
  • 2025年C++后端开发高频面试题深度解析:线程安全LRU缓存设计与实现
  • C# StringBuilder源码分析
  • 2025年Java最新社招面试八股文+技术场景题(金九银十)
  • Hadoop架构演进:从1.0到2.0的深度对比与优化解析
  • Hadoop(二)
  • QT技巧之快速搭建串口收发平台
  • Taro.getRandomValues() 用法详解
  • 有哪些好用的原型设计软件?墨刀、Axure等测评对比
  • Elasticsearch+Logstash+Kibana部署
  • Taro.eventCenter 用法详解与实战
  • 深入核心:理解Spring Boot的三大基石:起步依赖、自动配置与内嵌容器
  • 【Qt+error】error: use of undeclared identifier ‘MainWindow
  • uniapp各端通过webview实现互相通信
  • qt 中英文翻译 如何配置和使用
  • Spring AI 系列之十三 - RAG-加载本地嵌入模型