👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
13-MyBatis缓存机制与性能优化
📖 本文概述
本文是SSM框架系列MyBatis进阶篇的第三篇,将深入探讨MyBatis的缓存机制和性能优化策略。通过详细的原理分析和实践示例,帮助读者掌握MyBatis缓存的使用技巧和性能调优方法。
🎯 学习目标
- 深入理解MyBatis的一级缓存和二级缓存
- 掌握缓存的配置和使用方法
- 学会分析和解决缓存相关问题
- 了解MyBatis性能优化的最佳实践
- 掌握缓存失效和更新策略
1. MyBatis缓存机制概述
1.1 缓存的作用和意义
缓存是提高数据库应用性能的重要手段,MyBatis提供了强大的缓存机制:
java
/**
* 缓存机制的作用演示
*/
public class CacheDemo {
/**
* 无缓存的情况
*/
public void withoutCache() {
// 每次查询都会执行SQL
User user1 = userMapper.findById(1L); // 执行SQL: SELECT * FROM users WHERE id = 1
User user2 = userMapper.findById(1L); // 再次执行SQL: SELECT * FROM users WHERE id = 1
User user3 = userMapper.findById(1L); // 又执行SQL: SELECT * FROM users WHERE id = 1
// 结果:执行了3次相同的SQL查询
}
/**
* 有缓存的情况
*/
public void withCache() {
// 第一次查询执行SQL,后续从缓存获取
User user1 = userMapper.findById(1L); // 执行SQL: SELECT * FROM users WHERE id = 1
User user2 = userMapper.findById(1L); // 从缓存获取,不执行SQL
User user3 = userMapper.findById(1L); // 从缓存获取,不执行SQL
// 结果:只执行了1次SQL查询,性能提升显著
}
}
缓存的优势:
- 减少数据库访问 - 降低数据库负载
- 提高响应速度 - 内存访问比磁盘访问快得多
- 节省系统资源 - 减少网络传输和CPU消耗
- 提升用户体验 - 更快的页面加载速度
1.2 MyBatis缓存架构
MyBatis缓存架构图:
┌─────────────────────────────────────────────────────────────┐
│ 应用程序 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SqlSession │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 一级缓存 │ │
│ │ (Session级别) │ │
│ │ 默认开启,无法关闭 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ SqlSessionFactory │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 二级缓存 │ │
│ │ (Mapper级别) │ │
│ │ 需要手动配置开启 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 数据库 │
└─────────────────────────────────────────────────────────────┘
2. 一级缓存详解
2.1 一级缓存的特点
一级缓存是SqlSession级别的缓存,具有以下特点:
java
/**
* 一级缓存演示
*/
@Test
public void testFirstLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
System.out.println("=== 一级缓存测试 ===");
// 第一次查询,执行SQL
System.out.println("第一次查询:");
User user1 = userMapper.findById(1L);
System.out.println("用户信息:" + user1);
// 第二次查询,从缓存获取
System.out.println("第二次查询:");
User user2 = userMapper.findById(1L);
System.out.println("用户信息:" + user2);
// 验证是否为同一对象
System.out.println("是否为同一对象:" + (user1 == user2)); // true
sqlSession.close();
}
一级缓存的特点:
- 默认开启 - 无法关闭,始终有效
- Session级别 - 同一个SqlSession内有效
- 自动管理 - 无需手动配置
- 生命周期短 - SqlSession关闭时缓存清空
2.2 一级缓存的失效情况
java
/**
* 一级缓存失效场景演示
*/
@Test
public void testFirstLevelCacheInvalidation() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 场景1:不同SqlSession
System.out.println("=== 场景1:不同SqlSession ===");
User user1 = userMapper.findById(1L); // 执行SQL
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = userMapper2.findById(1L); // 再次执行SQL
System.out.println("不同Session查询结果相等:" + user1.equals(user2)); // true
System.out.println("不同Session是否同一对象:" + (user1 == user2)); // false
// 场景2:执行更新操作
System.out.println("=== 场景2:执行更新操作 ===");
User user3 = userMapper.findById(1L); // 从缓存获取
// 执行更新操作,缓存被清空
User updateUser = new User();
updateUser.setId(2L);
updateUser.setUsername("updated");
userMapper.update(updateUser);
User user4 = userMapper.findById(1L); // 重新执行SQL
System.out.println("更新后是否同一对象:" + (user3 == user4)); // false
// 场景3:手动清空缓存
System.out.println("=== 场景3:手动清空缓存 ===");
User user5 = userMapper.findById(1L); // 从缓存获取
sqlSession.clearCache(); // 手动清空缓存
User user6 = userMapper.findById(1L); // 重新执行SQL
System.out.println("清空缓存后是否同一对象:" + (user5 == user6)); // false
sqlSession.close();
sqlSession2.close();
}
2.3 一级缓存的配置
xml
<!-- mybatis-config.xml中的一级缓存配置 -->
<settings>
<!-- 本地缓存机制,SESSION或STATEMENT -->
<setting name="localCacheScope" value="SESSION"/>
</settings>
localCacheScope配置说明:
- SESSION(默认):缓存在整个SqlSession期间有效
- STATEMENT:缓存仅在语句执行期间有效,执行完立即清空
3. 二级缓存详解
3.1 二级缓存的特点
二级缓存是Mapper级别的缓存,需要手动配置:
xml
<!-- 1. 在mybatis-config.xml中开启二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<!-- 2. 在Mapper.xml中配置缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache/>
<!-- 或者自定义缓存配置 -->
<cache eviction="LRU"
flushInterval="60000"
size="512"
readOnly="false"/>
<select id="findById" parameterType="long" resultType="User" useCache="true">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
3.2 二级缓存配置详解
xml
<!-- 详细的二级缓存配置 -->
<cache
eviction="LRU" <!-- 缓存回收策略 -->
flushInterval="60000" <!-- 缓存刷新间隔(毫秒) -->
size="512" <!-- 缓存对象数量 -->
readOnly="false" <!-- 是否只读 -->
blocking="false" <!-- 是否阻塞 -->
type="org.mybatis.caches.ehcache.EhcacheCache"/> <!-- 自定义缓存实现 -->
配置参数说明:
-
eviction(回收策略)
- LRU(默认):最近最少使用,移除最长时间不被使用的对象
- FIFO:先进先出,按对象进入缓存的顺序来移除
- SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
- WEAK:弱引用,更积极地基于垃圾收集器状态和弱引用规则移除对象
-
flushInterval(刷新间隔)
- 缓存多长时间清空一次,默认不清空
- 单位:毫秒
-
size(缓存大小)
- 缓存存放多少元素,默认值是1024
-
readOnly(只读)
- true:只读缓存,返回缓存对象的相同实例,速度快但不安全
- false(默认):读写缓存,返回缓存对象的拷贝,速度慢但安全
3.3 二级缓存使用示例
java
/**
* 二级缓存演示
*/
@Test
public void testSecondLevelCache() {
// 第一个SqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
System.out.println("=== 第一个SqlSession查询 ===");
User user1 = userMapper1.findById(1L); // 执行SQL,结果放入二级缓存
System.out.println("用户信息:" + user1);
sqlSession1.close(); // 关闭session,一级缓存清空,二级缓存保留
// 第二个SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println("=== 第二个SqlSession查询 ===");
User user2 = userMapper2.findById(1L); // 从二级缓存获取,不执行SQL
System.out.println("用户信息:" + user2);
System.out.println("对象内容相等:" + user1.equals(user2)); // true
System.out.println("是否同一对象:" + (user1 == user2)); // false(因为是拷贝)
sqlSession2.close();
}
3.4 自定义缓存实现
java
/**
* 自定义Redis缓存实现
*/
public class RedisCache implements Cache {
private final String id;
private RedisTemplate<String, Object> redisTemplate;
public RedisCache(String id) {
this.id = id;
// 初始化Redis连接
this.redisTemplate = SpringContextHolder.getBean(RedisTemplate.class);
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
String redisKey = generateKey(key);
redisTemplate.opsForValue().set(redisKey, value, 30, TimeUnit.MINUTES);
}
@Override
public Object getObject(Object key) {
String redisKey = generateKey(key);
return redisTemplate.opsForValue().get(redisKey);
}
@Override
public Object removeObject(Object key) {
String redisKey = generateKey(key);
Object value = redisTemplate.opsForValue().get(redisKey);
redisTemplate.delete(redisKey);
return value;
}
@Override
public void clear() {
Set<String> keys = redisTemplate.keys(id + ":*");
if (keys != null && !keys.isEmpty()) {
redisTemplate.delete(keys);
}
}
@Override
public int getSize() {
Set<String> keys = redisTemplate.keys(id + ":*");
return keys != null ? keys.size() : 0;
}
private String generateKey(Object key) {
return id + ":" + key.toString();
}
}
xml
<!-- 使用自定义缓存 -->
<cache type="com.example.cache.RedisCache">
<property name="timeout" value="1800"/>
</cache>
4. 缓存失效和更新策略
4.1 缓存失效场景
java
/**
* 缓存失效场景演示
*/
@Test
public void testCacheInvalidation() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 1. 查询数据,放入缓存
User user1 = userMapper.findById(1L);
System.out.println("第一次查询:" + user1);
// 2. 执行更新操作,缓存失效
User updateUser = new User();
updateUser.setId(1L);
updateUser.setUsername("updated_name");
userMapper.update(updateUser);
sqlSession.commit(); // 提交事务,二级缓存失效
// 3. 再次查询,重新执行SQL
User user2 = userMapper.findById(1L);
System.out.println("更新后查询:" + user2);
sqlSession.close();
}
4.2 缓存更新策略
xml
<!-- 配置缓存刷新策略 -->
<mapper namespace="com.example.mapper.UserMapper">
<cache flushInterval="60000"/> <!-- 60秒自动刷新 -->
<!-- 查询语句使用缓存 -->
<select id="findById" parameterType="long" resultType="User" useCache="true">
SELECT * FROM users WHERE id = #{id}
</select>
<!-- 更新语句刷新缓存 -->
<update id="update" parameterType="User" flushCache="true">
UPDATE users
SET username = #{username}, email = #{email}
WHERE id = #{id}
</update>
<!-- 插入语句刷新缓存 -->
<insert id="insert" parameterType="User" flushCache="true">
INSERT INTO users (username, email, password)
VALUES (#{username}, #{email}, #{password})
</insert>
<!-- 删除语句刷新缓存 -->
<delete id="deleteById" parameterType="long" flushCache="true">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
4.3 跨Mapper缓存管理
xml
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<cache-ref namespace="com.example.mapper.CommonCache"/>
<select id="findById" parameterType="long" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
<!-- RoleMapper.xml -->
<mapper namespace="com.example.mapper.RoleMapper">
<cache-ref namespace="com.example.mapper.CommonCache"/>
<select id="findByUserId" parameterType="long" resultType="Role">
SELECT * FROM roles r
INNER JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = #{userId}
</select>
</mapper>
<!-- CommonCache.xml -->
<mapper namespace="com.example.mapper.CommonCache">
<cache eviction="LRU" flushInterval="300000" size="1024" readOnly="false"/>
</mapper>
5. 性能优化最佳实践
5.1 SQL优化
xml
<!-- 优化前:N+1查询问题 -->
<select id="findAllUsers" resultMap="UserResultMap">
SELECT * FROM users
</select>
<select id="findRolesByUserId" parameterType="long" resultType="Role">
SELECT * FROM roles r
INNER JOIN user_roles ur ON r.id = ur.role_id
WHERE ur.user_id = #{userId}
</select>
<!-- 优化后:一次查询获取所有数据 -->
<resultMap id="UserWithRolesResultMap" type="User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="email" property="email"/>
<collection property="roles" ofType="Role">
<id column="role_id" property="id"/>
<result column="role_name" property="roleName"/>
<result column="role_description" property="description"/>
</collection>
</resultMap>
<select id="findAllUsersWithRoles" resultMap="UserWithRolesResultMap">
SELECT
u.id as user_id, u.username, u.email,
r.id as role_id, r.role_name, r.description as role_description
FROM users u
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.id
</select>
5.2 分页查询优化
xml
<!-- 物理分页 -->
<select id="findUsersByPage" parameterType="map" resultType="User">
SELECT * FROM users
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
<!-- 统计总数(使用缓存) -->
<select id="countUsers" parameterType="map" resultType="long" useCache="true">
SELECT COUNT(*) FROM users
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</where>
</select>
5.3 批量操作优化
xml
<!-- 批量插入优化 -->
<insert id="batchInsertUsers" parameterType="list">
INSERT INTO users (username, email, password, age)
VALUES
<foreach collection="list" item="user" separator=",">
(#{user.username}, #{user.email}, #{user.password}, #{user.age})
</foreach>
</insert>
<!-- 批量更新优化 -->
<update id="batchUpdateUsers" parameterType="list">
<foreach collection="list" item="user" separator=";">
UPDATE users
SET username = #{user.username}, email = #{user.email}
WHERE id = #{user.id}
</foreach>
</update>
5.4 连接池优化
xml
<!-- Druid连接池优化配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<!-- 基本配置 -->
<property name="driverClassName" value="${database.driver}"/>
<property name="url" value="${database.url}"/>
<property name="username" value="${database.username}"/>
<property name="password" value="${database.password}"/>
<!-- 连接池配置 -->
<property name="initialSize" value="10"/> <!-- 初始连接数 -->
<property name="minIdle" value="10"/> <!-- 最小空闲连接数 -->
<property name="maxActive" value="50"/> <!-- 最大活跃连接数 -->
<property name="maxWait" value="60000"/> <!-- 获取连接最大等待时间 -->
<!-- 性能优化 -->
<property name="poolPreparedStatements" value="true"/>
<property name="maxPoolPreparedStatementPerConnectionSize" value="20"/>
<!-- 连接检测 -->
<property name="validationQuery" value="SELECT 1"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<!-- 监控配置 -->
<property name="filters" value="stat,wall,slf4j"/>
</bean>
6. 缓存监控和调试
6.1 缓存统计信息
java
/**
* 缓存统计工具类
*/
@Component
public class CacheStatistics {
@Autowired
private SqlSessionFactory sqlSessionFactory;
/**
* 获取缓存统计信息
*/
public void printCacheStatistics() {
Configuration configuration = sqlSessionFactory.getConfiguration();
// 获取所有Mapper的缓存信息
Collection<Cache> caches = configuration.getCaches();
System.out.println("=== MyBatis缓存统计信息 ===");
for (Cache cache : caches) {
System.out.println("缓存ID: " + cache.getId());
System.out.println("缓存大小: " + cache.getSize());
System.out.println("缓存类型: " + cache.getClass().getSimpleName());
System.out.println("---");
}
}
/**
* 清空所有缓存
*/
public void clearAllCaches() {
Configuration configuration = sqlSessionFactory.getConfiguration();
Collection<Cache> caches = configuration.getCaches();
for (Cache cache : caches) {
cache.clear();
}
System.out.println("所有缓存已清空");
}
}
6.2 缓存调试配置
xml
<!-- logback.xml中配置MyBatis缓存日志 -->
<configuration>
<!-- MyBatis缓存相关日志 -->
<logger name="org.apache.ibatis.cache" level="DEBUG"/>
<logger name="com.example.mapper" level="DEBUG"/>
<!-- SQL执行日志 -->
<logger name="org.apache.ibatis.logging.jdbc" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
7. 小结
本文深入介绍了MyBatis的缓存机制和性能优化:
- 一级缓存:SqlSession级别,默认开启,自动管理
- 二级缓存:Mapper级别,需要配置,支持自定义实现
- 缓存配置:回收策略、刷新间隔、大小限制等
- 性能优化:SQL优化、分页优化、批量操作、连接池调优
- 监控调试:缓存统计、日志配置、问题排查
掌握MyBatis缓存机制的关键点:
- 理解一级缓存和二级缓存的区别和适用场景
- 合理配置缓存参数,避免内存溢出
- 注意缓存失效时机,保证数据一致性
- 结合业务场景选择合适的缓存策略
- 定期监控缓存效果,及时调优
🔗 下一篇预告
下一篇文章将介绍MyBatis与Spring集成,学习如何在Spring环境中使用MyBatis,以及事务管理的最佳实践。
相关文章: