1. 缓存的概念
缓存(Cache)是一种用于存储临时数据的机制,其核心目的是通过保存频繁访问或计算成本高的数据副本,来提升系统性能和响应速度。缓存通常位于数据访问路径的中间层,介于数据消费者(如应用程序)和数据源(如数据库、远程API)之间。
主要特点:
-
高速存取:缓存通常使用内存等高速存储介质,相比磁盘或网络I/O可提供更快的访问速度
-
数据暂存:缓存数据通常是原始数据的副本,具有临时性特征
-
智能淘汰:当缓存空间不足时,会通过LRU(最近最少使用)、FIFO等算法自动淘汰旧数据
常见应用场景:
-
CPU缓存:现代处理器中的L1/L2/L3缓存,用于减少访问主内存的延迟
-
Web缓存:浏览器缓存静态资源(如图片、CSS),CDN缓存网站内容
-
数据库缓存:如Redis缓存查询结果,MySQL的查询缓存
-
应用缓存:Memcached缓存计算结果,减轻后端负担
典型工作流程示例:
-
应用首先检查缓存中是否存在所需数据
-
若存在(缓存命中),则直接使用缓存数据
-
若不存在(缓存未命中),则从原始数据源获取
-
将获取的数据存入缓存供后续使用
-
根据预设策略定期更新或失效缓存数据
缓存的关键指标:
-
命中率:成功从缓存获取数据的请求比例
-
延迟:从缓存获取数据所需时间
-
一致性:缓存数据与源数据的同步程度
-
吞吐量:单位时间内可处理的缓存请求数
注意事项:
-
需要考虑缓存雪崩、缓存穿透等问题
-
需要平衡缓存一致性与性能的关系
-
大数据量场景下需注意内存管理
2. Mybatis一级缓存
Mybatis 一级缓存是 SqlSession 级别的缓存,当同一个 SqlSession 执行相同的 SQL 语句时,Mybatis 会优先从缓存中获取结果,而不是直接查询数据库。
核心特性:
-
作用范围 :一级缓存的生命周期与SqlSession相同,只在单个SqlSession内有效
-
自动启用:一级缓存默认开启,无需额外配置
-
缓存策略:基于PerpetualCache实现,底层是Map集合存储缓存数据,Map<k,v> k指SQL语句 v指查询出来的值
java
/**
* 证明一级缓存的存在
* MyBatis的一级缓存也是SqlSession的缓存 factory工厂对象 -> sqlSession对象 -> mapper代理对象
* 不同的SqlSession之间的缓存不能互相访问
* 一级缓存底层是Map集合
* Map<k,v> k指SQL语句 v指查询出来的值
*/
@Test
public void run1() throws IOException {
//加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//通过流创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//获取到sqlsession对象,没有设置事务的方式,默认手动提交
SqlSession sqlSession = factory.openSession();
//获取代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用方法 通过主键查询
//先查询一级缓存,没有数据。会查数据库,都会有SQL语句,把查询出的数据存储到一级缓存中
User user = mapper.findById(1);
//打印地址
System.out.println(user);
System.out.println("==================================");
//变式1 若执行手动清空缓存,则两条sql语句
//sqlSession.clearCache();
//变式2 日志会有一条sql语句,因为两个代理对象都是一个sqlSession对象
//UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
//User user1=mapper1.findById(1);
//变式3 两个sqlSession对象,缓存不能互相访问,则会有两条sql语句
//SqlSession sqlSession1 = factory.openSession();
//UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
//再查询一次
//同一个mapper代理对象,先查询一级缓存,存在数据。从缓存中把数据返回,就没有SQL语句
User user1=mapper.findById(1);
//这样两个user对象地址一样
System.out.println(user1);
sqlSession.close();
inputStream.close();
}
3. Mybatis二级缓存
MyBatis二级缓存是跨SqlSession的缓存,其生命周期与整个应用相同。与一级缓存(SqlSession级别)不同,二级缓存可以实现多个SqlSession共享数据。
在全局xml中配置
XML
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消极加载及按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
在Xxxmapper.xml中配置
XML
<!--开启二级缓存使用-->
<cache/>
java
/**
* 二级缓存是工厂factory的缓存 factory工厂对象 -> sqlSession对象 -> mapper代理对象
* 证明二级缓存 (二级缓存的使用对象地址不同 但是也是从缓存加载
* 原因是二级缓存存储的是零散数据 组装出来的对象)
* 演示二级缓存
*/
@Test
public void run2() throws IOException {
//加载配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//通过流创建工厂对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//获取到sqlsession对象,没有设置事务的方式,默认手动提交
SqlSession sqlSession = factory.openSession();
//获取代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//调用方法 通过主键查询
//先查询一级缓存,没有数据。会查数据库,都会有SQL语句,把查询出的数据存储到一级缓存中
User user = mapper.findById(1);
//打印地址
System.out.println(user);
System.out.println("=====================");
//手动清空缓存
sqlSession.clearCache();
sqlSession.commit();
//关闭session
sqlSession.close();
//同一个factory工厂的不同sqlSession对象,对二级缓存没影响
SqlSession sqlSession1=factory.openSession();
//变式1 创新一个新的工厂,让sqlSession1走新工厂,缓存不能互相访问,则会有两条sql语句
//注意,创建新的工厂需重新获取流
//InputStream inputStream1 = Resources.getResourceAsStream("SqlMapConfig.xml");
//SqlSessionFactory factory1 = new SqlSessionFactoryBuilder().build(inputStream1);
//SqlSession sqlSession1=factory1.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
//再查询一次
//先查询一级缓存,存在数据。从缓存中把数据返回,就没有SQL语句
User user1=mapper1.findById(1);
//这样两个user对象地址一样
System.out.println(user1);
sqlSession1.close();
inputStream.close();
}