在当今高并发的互联网应用中,数据库的查询性能往往是系统的瓶颈。频繁地对数据库进行相同的查询,会导致大量的 I/O 操作和数据库负载,从而降低系统的响应速度。为了解决这个问题,缓存技术应运而生。MyBatis 作为一款优秀的持久层框架,内置了强大的缓存机制,可以显著提升数据库查询性能。本文将详细解析 MyBatis 的一级缓存和二级缓存。
一、什么是缓存?
1. 缓存的定义
缓存(Cache)是指将计算过程中产生的中间数据或结果数据,临时存储在速度更快的存储介质(通常是内存)中,以便后续需要时可以快速访问,从而避免重复计算或重复从慢速存储介质(如磁盘、数据库)中读取数据。
简单来说,缓存就是存储在内存中的临时数据。
2. 为什么使用缓存?
使用缓存主要有以下几个核心目的:
减少数据库交互:避免了对数据库的频繁查询,极大地减轻了数据库的压力。
提升系统性能:内存的读写速度远快于磁盘,从缓存中读取数据可以显著缩短响应时间,提高系统吞吐量。
降低系统开销:减少了网络 I/O 和数据库 I/O,降低了系统的整体资源消耗。
3. 什么样的数据适合放入缓存?
并非所有数据都适合缓存。通常,适合放入缓存的数据需要满足以下特点:
查询频率高:被大量用户或频繁访问的数据。
修改频率低:数据相对稳定,不会经常发生变化。如果数据频繁修改,会导致缓存与数据库的数据不一致,需要频繁更新缓存,反而可能得不偿失。
一致性要求不高:对于一些实时性要求不高的数据,即使缓存中的数据有短暂的延迟,也是可以接受的。
典型的例子包括:网站的分类信息、用户的基本配置、商品的基础信息等。
二、MyBatis 缓存机制
MyBatis 提供了非常强大且易于配置的查询缓存特性。它默认定义了两级缓存:一级缓存 和二级缓存。
- 一级缓存 :默认开启,作用于
SqlSession级别。 - 二级缓存 :默认关闭,需要手动开启和配置,作用于
Mapper的namespace级别。
2.1. 一级缓存
一级缓存也称为本地缓存,它是 MyBatis 中默认开启的缓存。
2.1.1工作原理
一级缓存的生命周期与 SqlSession 完全相同。在同一次数据库会话(即同一个 SqlSession 实例)期间,如果执行了完全相同的查询语句,MyBatis 会将查询结果存入一级缓存中。当再次执行相同的查询时,MyBatis 会优先从一级缓存中获取数据,而不会再去查询数据库,从而提高查询效率。

2.1.2代码演示
java
public class UserTest {
private InputStream in = null;
private SqlSession session = null;
private UserDao mapper = null;
/**
* 测试查询所有的方法
*/
@Test
public void findById() throws IOException {
//加载主配置文件,目的是为了构建SqlSessionFactory对象
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//通过SqlSessionFactory工厂对象创建SqlSesssion对象
session = factory.openSession();
//通过Session创建UserDao接口代理对象
mapper = session.getMapper(UserDao.class);
User user1 = mapper.findById(1);
System.out.println(user1.toString());
System.out.println("-----------------");
User user2 = mapper.findById(1);
System.out.println(user2.toString());
System.out.println(user1 == user2);
//释放资源
session.close();
in.close();
}
}

2.1.3缓存失效的情况
sqlSession不同
sqlSession相同,查询条件不同
sqlSession相同,两次查询之间执行了增删改操作!
sqlSession相同,手动清除一级缓存
1.sqlSession不同


2.sqlSession相同,查询条件不同,多次查询不同的情况,不会导致缓存失效


3.sqlSession相同,两次查询之间执行了增删改操作!


4.sqlSession相同,手动清除一级缓存


2.2二级缓存
2.2.1工作原理
当一个 SqlSession 关闭时,其对应的一级缓存中的数据会被写入到二级缓存中。此后,其他由同一个 SqlSessionFactory 创建的 SqlSession 在执行相同的查询时,就可以直接从二级缓存中获取数据,而无需再访问数据库。
2.2.2二级缓存开启的条件
1.在核心配置文件中,设置全局属性caheEnable="true"。
2.在映射件中置
3.查询数据所转换的实体类类型必须实现序列化接口
4.二级缓存必须在SqlSession关闭或提交之后有效
2.2.3二级缓存失效的情况
两次查询之间行了任意的增删改,会使得一级二级缓存同时失效
2.2.4开启二级缓存
在SqlMapConfig.xml配置文件中开启二级缓存
java
<!‐‐ 开启二级缓存 ‐‐>
<settings>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
在UserDao.xml配置文件声明使用二级缓存
java
<!--使用二级缓存-->
<cache/>
查询数据所转换的实体类类型必须实现序列化接口
序列化是指将一个对象的状态转换成字节流的过程,而反序列化则是将字节流恢复成对象状态的过程。
java
public class User implements Serializable {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// get set方法 .....
}
二级缓存必须在SqlSession关闭或提交之后有效
2.2.5代码演示
java
@Test
public void findById() throws IOException {
// 1.加载SqlMapConfig配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlSessionFactory工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3.sqlSessionFactory创建sqlSession
SqlSession sqlSession = factory.openSession();
SqlSession sqlSession2 = factory.openSession();
//4.通过Session创建UserDao接口代理对象
UserDao mapper = sqlSession.getMapper(UserDao.class);
User user1 = mapper.findById(1);
System.out.println(user1.toString());
// 将其一级缓存的数据放进二级缓存中,并清空一级缓存
sqlSession.close();
System.out.println("-----------------");
UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
User user2 = mapper2.findById(1);
System.out.println(user2.toString());
// 将其一级缓存的数据放进二级缓存中,并清空一级缓存
sqlSession2.close();
System.out.println(user1 == user2);
resourceAsStream.close();
}
测试结果

2.2.6重要结论

打印发现2个对象的地址值不一样,但是确实只发送了一次SQL语句的查询,二级缓存中存储的是数据,不是对象。
2.2.7Catch参数的具体细节
1.eviction(收回策略)
LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
FIFO(先进先出):按对象进入缓存的顺序来移除它们。
SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。
2.flushinterval(刷新间隔) 可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。
默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。
3.size(引用数目)
可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。
4.readOnly(只读) 属性可以被设置为 true / false。
true:只读缓存:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改, 这提供了很重要的性能优势。
false读写缓存: 通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。
三、Mybatis缓存查询顺序
1.先查询二级缓存,因为二级缓存中可能会有其他程序查询出来的数据,可以直接拿来使用
2.如果二级缓存未命中,再查询一级缓存
3.如果一级缓存也没有命中,则查询数据库
4.SqlSession关闭之后,一级缓存的数据会写入二级缓存
