MyBatis 缓存机制详解
目录
一、什么是缓存?
1. 什么是缓存?
2. 为什么使用缓存?
3. 什么样的数据适合使用缓存?
二、MyBatis 缓存机制
1. 一级缓存(也叫本地缓存)
2. 一级缓存失效的情况
3. 二级缓存
4. 二级缓存失效的情况
三、MyBatis 缓存查询顺序
四、MyBatis 缓存的高级配置
前言
在高并发系统中,数据库查询是系统性能的瓶颈之一。为了提高查询效率,减少数据库的访问次数,缓存机制应运而生。MyBatis 作为一款优秀的 ORM 框架,内置了强大的查询缓存特性,支持一级缓存和二级缓存两级缓存机制。本文将详细介绍缓存的概念、MyBatis 的缓存机制以及如何配置和使用 MyBatis 缓存。
一、什么是缓存?
1. 什么是缓存?
缓存是存储在内存中的数据。
当用户查询数据时,系统可以直接从缓存中获取,而不需要访问数据库,从而提升查询效率。
2. 为什么使用缓存?
- 减少数据库交互,降低系统的开销。
- 提高查询效率,优化系统性能。
- 支持高并发,避免数据库成为系统瓶颈。
3. 什么样的数据适合使用缓存?
- 经常被查询
- 变化不频繁
对于频繁变更的数据(如订单状态),缓存并不适用,因为数据不一致的风险较高。
二、MyBatis 缓存机制
MyBatis 内置了强大的缓存机制,支持两级缓存:
- 一级缓存(本地缓存):默认开启,作用域为 SqlSession 级别
- 二级缓存(全局缓存):需要手动开启,作用域为 SqlSessionFactory 级别
1. 一级缓存(也叫本地缓存)
在使用一级缓存后,与数据库第一次会话(SqlSession )期间查询到的数据会被放入本地缓存当中。如果在同一个会话后续需要获取相同的数据,可以直接从缓存中获取,而没必要再去查询数据库
(1)一级缓存的特点
- 默认开启,不需要额外配置。
- 作用域是SqlSession 级别,即同一个 SqlSession 多次执行相同查询,结果会被缓存。
- 不同 SqlSession 之间缓存互不影响。
(2)一级缓存示例
@Test
public void findById() throws IOException {
// 加载配置文件
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 创建 SqlSession
SqlSession session = factory.openSession();
UserDao mapper = session.getMapper(UserDao.class);
// 第一次查询
User user1 = mapper.findById(1);
System.out.println(user1.toString());
// 第二次查询(相同的查询)
User user2 = mapper.findById(1);
System.out.println(user2.toString());
// 判断两次查询结果是否相同
System.out.println(user1 == user2); // true,说明数据来自缓存
session.close();
in.close();
}
运行如下:
结论: 在同一个 SqlSession (会话)下,第一次查询的数据会缓存,第二次查询相同数据时直接从缓存中获取,不会执行 SQL 语句。
2. 一级缓存失效的情况
以下情况会导致 MyBatis 一级缓存失效:
(1).不同 SqlSession
如果两次查询使用的是不同的 SqlSession,则缓存不会共享,查询会直接访问数据库。
(2).查询条件不同
因为查询条件不同,缓存未命中。
mapper.findById(1);
mapper.findById(2);
(3).执行了增删改操作
任何增删改操作都会清空 SqlSession 的缓存,以确保数据一致性。
mapper.findById(1);
mapper.updateUser(user);
mapper.findById(1);
(4).手动清除缓存
手动清除一级缓存后,查询将重新访问数据库。
session.clearCache();
3. 二级缓存
(1)二级缓存的特点
- 默认关闭,需要手动开启。
- 作用域是SqlSessionFactory 级别,即同一 SqlSessionFactory 产生的多个 SqlSession 共享缓存。
- 缓存的数据是对象的副本,而不是对象本身(即缓存的是数据,不是对象实例)。
(2)开启二级缓存的条件
-
在核心配置文件(SqlMapConfig.xml)中开启全局缓存
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在对应的 Mapper 配置文件中声明使用二级缓存
<!--使用二级缓存--> <cache/>
-
实体类必须实现
Serializable
接口public class User implements Serializable { private Integer id; private String username; private Date birthday; private String sex; private String address; }
-
SqlSession 关闭或提交后,数据才会进入二级缓存
@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(); }
运行结果:
最后输出false,证明user1和user2并不是同一个对象;得出一下结论:
结论: 二级缓存存储的是数据,而不是对象,因此即使查询相同数据,返回的对象实例仍然不同。
4. 二级缓存失效的情况
- 执行了增删改操作:会清空一级缓存和二级缓存,以保证数据一致性。
三、MyBatis 缓存查询顺序
- 优先查询二级缓存
因为二级缓存是 SqlSessionFactory 级别,多个 SqlSession 可能已经缓存了数据。 - 如果二级缓存未命中,则查询一级缓存
- 如果一级缓存也未命中,则查询数据库
- SqlSession 关闭后,一级缓存数据会写入二级缓存
四、MyBatis 缓存的高级配置
在 Mapper.xml
文件中,可以自定义缓存策略:
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
Catch参数的具体细节说明:
eviction(收回策略)
- LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
- FIFO(先进先出):按对象进入缓存的顺序来移除它们。
- SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
- WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushinterval(刷新间隔)
- 可以被设置为任意的正整数,单位是毫秒。
- 默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。
size(缓存容量)
- 最大缓存对象数,可以被设置为任意正整数,默认值是1024 。
readOnly(只读) 属性可以被设置为 true / false。
- true只读缓存:所有调用者获取到的都是缓存对象本身,并且不能对其进行修改。 这提供了很重要的性能优势。
- false读写缓存: 通过序列化返回缓存对象的拷贝版,这种方式会慢一些,但是安全,因此默认是 false。
五、总结
- 一级缓存:默认开启,作用域是 SqlSession,缓存的是 查询结果,SqlSession 关闭后失效。
- 二级缓存:默认关闭,作用域是 SqlSessionFactory,多个 SqlSession 共享,需手动开启,SqlSession 关闭后数据进入二级缓存。
- 缓存查询顺序:二级缓存 → 一级缓存 → 数据库。
- 缓存失效情况:SqlSession 关闭、执行增删改操作、手动清除缓存等。
合理使用 MyBatis 缓存,可以有效提高查询性能,减少数据库访问压力,提高系统并发能力。