3.1 MyBatis 缓存机制 MyBatis 缓存机制详解 3.1.1 MyBatis 缓存概述 MyBatis 的缓存机制主要分为两级: 一级缓存(本地缓存): SqlSession 级别的缓存,默认开启,生命周期与 SqlSession 一致。 二级缓存(全局缓存): Mapper 级别的缓存,需要手动开启和配置,多个 SqlSession 可以共享。 为什么需要缓存? 数据库访问通常是应用程序中最耗时的操作之一。 通过缓存,我们可以将经常访问的数据存储在内存中,下次需要相同数据时,直接从缓存中获取,避免了重复的数据库查询,从而显著提高应用程序的性能和响应速度。 缓存的优缺点: 优点: 提升查询性能,减少数据库压力。 降低网络延迟。 提高用户体验。
MyBatis 的缓存机制主要分为两级:
一级缓存(本地缓存): SqlSession 级别的缓存,默认开启,生命周期与 SqlSession 一致。
二级缓存(全局缓存): Mapper 级别的缓存,需要手动开启和配置,多个 SqlSession 可以共享。
为什么需要缓存?
数据库访问通常是应用程序中最耗时的操作之一。 通过缓存,我们可以将经常访问的数据存储在内存中,下次需要相同数据时,直接从缓存中获取,避免了重复的数据库查询,从而显著提高应用程序的性能和响应速度。
缓存的优缺点:
优点:
提升查询性能,减少数据库压力。
降低网络延迟。
提高用户体验。
缺点:
可能导致数据不一致,需要考虑缓存失效策略。
增加了应用程序的复杂性。
需要消耗额外的内存资源。
一级缓存是 MyBatis 内置的缓存,也称为本地缓存。它基于 SqlSession 存在,并且默认开启。
工作原理:
当 SqlSession 执行查询操作时,MyBatis 会先检查一级缓存中是否存在对应的缓存数据。
如果缓存命中,则直接从缓存中返回结果,避免数据库查询。
如果缓存未命中,则执行数据库查询,并将查询结果存入一级缓存。
当 SqlSession 关闭或执行更新、插入、删除操作时,一级缓存会被清空。
特点:
SqlSession 级别: 每个 SqlSession 都有自己独立的一级缓存。
默认开启: 无需配置,自动生效。
生命周期短: 与 SqlSession 的生命周期一致,SqlSession 关闭时缓存失效。
不可控: 无法手动配置缓存失效策略。
代码实践:
假设我们有一个 UserMapper.xml 文件,包含以下查询语句:
<!-- UserMapper.xml --> <mapper namespace="com.example.mapper.UserMapper"> <select id="getUserById" resultType="com.example.entity.User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
Java 代码:
// 获取 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取 SqlSession try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 第一次查询,会执行数据库查询 User user1 = userMapper.getUserById(1); System.out.println("First query: " + user1); // 第二次查询,直接从一级缓存获取 User user2 = userMapper.getUserById(1); System.out.println("Second query: " + user2); // 执行更新操作,清空一级缓存 user1.setName("New Name"); userMapper.updateUser(user1); sqlSession.commit(); // 第三次查询,会执行数据库查询 User user3 = userMapper.getUserById(1); System.out.println("Third query: " + user3); }
代码解释:
第一次查询 getUserById(1) 时,一级缓存为空,MyBatis 会执行数据库查询,并将结果放入一级缓存。
第二次查询 getUserById(1) 时,一级缓存命中,直接从缓存中返回结果,不会执行数据库查询。
执行 updateUser 操作后,MyBatis 会清空一级缓存,确保数据一致性。
第三次查询 getUserById(1) 时,一级缓存已被清空,MyBatis 会再次执行数据库查询。
注意事项:
一级缓存是基于 SqlSession 的,因此在不同的 SqlSession 中,即使执行相同的查询,也会执行数据库查询。
当执行更新、插入、删除操作时,MyBatis 会自动清空一级缓存,以保证数据一致性。
一级缓存无法手动配置失效策略,其生命周期与 SqlSession 一致。
二级缓存是 Mapper 级别的缓存,多个 SqlSession 可以共享。 需要手动开启和配置。
工作原理:
当 SqlSession 执行查询操作时,MyBatis 会先检查二级缓存中是否存在对应的缓存数据。
如果缓存命中,则直接从缓存中返回结果,避免数据库查询。
如果缓存未命中,则执行数据库查询,并将查询结果存入二级缓存。
当 Mapper 中执行更新、插入、删除操作时,会清空该 Mapper 对应的二级缓存。
特点:
Mapper 级别: 多个 SqlSession 可以共享同一个 Mapper 的二级缓存。
手动开启和配置: 需要在 mybatis-config.xml 和 Mapper XML 文件中进行配置。
生命周期长: 比一级缓存更长,可以跨 SqlSession 存在。
可配置: 可以配置缓存的失效策略、大小等。
配置步骤:
在 mybatis-config.xml 中开启二级缓存:
<configuration> <settings> <setting name="cacheEnabled" value="true"/> </settings> <!-- 其他配置 --> </configuration>
在 Mapper XML 文件中配置 <cache> 元素:
<!-- UserMapper.xml --> <mapper namespace="com.example.mapper.UserMapper"> <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/> <select id="getUserById" resultType="com.example.entity.User"> SELECT * FROM user WHERE id = #{id} </select> <update id="updateUser" parameterType="com.example.entity.User"> UPDATE user SET name = #{name} WHERE id = #{id} </update> </mapper>
<cache> 元素属性说明:
eviction: 缓存回收策略,可选值包括:
LRU (Least Recently Used):最近最少使用,移除最长时间不被使用的对象。
FIFO (First In First Out):先进先出,按对象进入缓存的顺序移除。
SOFT: 软引用,移除基于垃圾回收器状态和软引用规则的对象。
WEAK: 弱引用,更积极地移除基于垃圾回收器状态和弱引用规则的对象。
flushInterval: 缓存刷新间隔,单位为毫秒。 默认为不刷新。
size: 缓存中存放对象的最大数量。 默认为 1024。
readOnly: 是否只读。 如果设置为 true,则所有从缓存中获取的对象都是只读的,不允许修改,可以提高并发性能。 如果设置为 false,则从缓存中获取的对象是可修改的,但会降低并发性能。
type: 自定义缓存实现类。
实体类需要实现 Serializable 接口:
因为二级缓存需要将对象序列化到磁盘或网络中,所以实体类必须实现 Serializable 接口。
import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 1L; // 属性 }
代码实践:
// 获取 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 第一次 SqlSession try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) { UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class); // 第一次查询,会执行数据库查询,并将结果放入二级缓存 User user1 = userMapper1.getUserById(1); System.out.println("First query (SqlSession 1): " + user1); } // 第二次 SqlSession try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) { UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class); // 第二次查询,直接从二级缓存获取 User user2 = userMapper2.getUserById(1); System.out.println("Second query (SqlSession 2): " + user2); } // 第三次 SqlSession try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) { UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class); // 执行更新操作,清空二级缓存 User user = userMapper3.getUserById(1); user.setName("Updated Name"); userMapper3.updateUser(user); sqlSession3.commit(); // 第四次查询,会执行数据库查询 User user3 = userMapper3.getUserById(1); System.out.println("Third query (SqlSession 3): " + user3); }
代码解释:
第一次查询 getUserById(1) 时,二级缓存为空,MyBatis 会执行数据库查询,并将结果放入二级缓存。
第二次查询 getUserById(1) 时,虽然在不同的 SqlSession 中,但二级缓存命中,直接从缓存中返回结果,不会执行数据库查询。
执行 updateUser 操作后,MyBatis 会清空 UserMapper 对应的二级缓存。
第四次查询 getUserById(1) 时,二级缓存已被清空,MyBatis 会再次执行数据库查询。
注意事项:
需要在 mybatis-config.xml 和 Mapper XML 文件中进行配置才能开启二级缓存。
实体类必须实现 Serializable 接口。
当 Mapper 中执行更新、插入、删除操作时,会清空该 Mapper 对应的二级缓存。
可以使用 <cache-ref> 元素引用其他 Mapper 的缓存配置。
缓存流程图:
缓存失效策略至关重要,直接影响缓存的有效性和数据一致性。
基于时间的失效: 设置缓存的过期时间,超过时间后缓存失效。
基于容量的失效: 设置缓存的最大容量,超过容量后根据一定的算法(如 LRU、FIFO)移除缓存数据。
基于事件的失效: 当数据发生变更时,手动或自动清空缓存。
MyBatis 提供了 flushInterval 属性来配置基于时间的失效,以及 eviction 属性配置基于容量的失效策略。 基于事件的失效可以通过在更新、插入、删除操作后手动清空缓存来实现,也可以通过配置 MyBatis 的拦截器来实现自动清空缓存。
MyBatis 的缓存机制可以显著提高查询性能,减少数据库访问次数。 一级缓存是 SqlSession 级别的缓存,默认开启,生命周期短。 二级缓存是 Mapper 级别的缓存,需要手动开启和配置,多个 SqlSession 可以共享。 选择合适的缓存策略,并合理配置缓存失效策略,是使用 MyBatis 缓存的关键。
选择缓存的建议:
对于频繁访问且数据变化不频繁的数据,可以使用二级缓存。
对于需要保证数据实时性的数据,不建议使用缓存。
对于只读数据,可以将 readOnly 属性设置为 true,提高并发性能。
根据实际情况选择合适的缓存回收策略和刷新间隔。
在更新、插入、删除操作后,及时清空相关缓存,保证数据一致性。
希望本文能够帮助你更好地理解和使用 MyBatis 的缓存机制。