SSM从入门到实战: 2.6 MyBatis缓存机制与性能优化
👋 大家好,我是 阿问学长
!专注于分享优质开源项目
解析、毕业设计项目指导
支持、幼小初高
的教辅资料
推荐等,欢迎关注交流!🚀
13-MyBatis缓存机制与性能优化
📖 本文概述
本文是SSM框架系列MyBatis进阶篇的第三篇,将深入探讨MyBatis的缓存机制和性能优化策略。通过详细的原理分析和实践示例,帮助读者掌握MyBatis缓存的使用技巧和性能调优方法。
🎯 学习目标
- 深入理解MyBatis的一级缓存和二级缓存
- 掌握缓存的配置和使用方法
- 学会分析和解决缓存相关问题
- 了解MyBatis性能优化的最佳实践
- 掌握缓存失效和更新策略
1. MyBatis缓存机制概述
1.1 缓存的作用和意义
缓存是提高数据库应用性能的重要手段,MyBatis提供了强大的缓存机制:
/*** 缓存机制的作用演示*/
public class CacheDemo {/*** 无缓存的情况*/public void withoutCache() {// 每次查询都会执行SQLUser user1 = userMapper.findById(1L); // 执行SQL: SELECT * FROM users WHERE id = 1User user2 = userMapper.findById(1L); // 再次执行SQL: SELECT * FROM users WHERE id = 1User 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 = 1User user2 = userMapper.findById(1L); // 从缓存获取,不执行SQLUser user3 = userMapper.findById(1L); // 从缓存获取,不执行SQL// 结果:只执行了1次SQL查询,性能提升显著}
}
缓存的优势:
- 减少数据库访问 - 降低数据库负载
- 提高响应速度 - 内存访问比磁盘访问快得多
- 节省系统资源 - 减少网络传输和CPU消耗
- 提升用户体验 - 更快的页面加载速度
1.2 MyBatis缓存架构
MyBatis缓存架构图:┌─────────────────────────────────────────────────────────────┐
│ 应用程序 │
└─────────────────────────────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────┐
│ SqlSession │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 一级缓存 │ │
│ │ (Session级别) │ │
│ │ 默认开启,无法关闭 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────┐
│ SqlSessionFactory │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 二级缓存 │ │
│ │ (Mapper级别) │ │
│ │ 需要手动配置开启 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘│▼
┌─────────────────────────────────────────────────────────────┐
│ 数据库 │
└─────────────────────────────────────────────────────────────┘
2. 一级缓存详解
2.1 一级缓存的特点
一级缓存是SqlSession级别的缓存,具有以下特点:
/*** 一级缓存演示*/
@Test
public void testFirstLevelCache() {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);System.out.println("=== 一级缓存测试 ===");// 第一次查询,执行SQLSystem.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)); // truesqlSession.close();
}
一级缓存的特点:
- 默认开启 - 无法关闭,始终有效
- Session级别 - 同一个SqlSession内有效
- 自动管理 - 无需手动配置
- 生命周期短 - SqlSession关闭时缓存清空
2.2 一级缓存的失效情况
/*** 一级缓存失效场景演示*/
@Test
public void testFirstLevelCacheInvalidation() {SqlSession sqlSession = sqlSessionFactory.openSession();UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 场景1:不同SqlSessionSystem.out.println("=== 场景1:不同SqlSession ===");User user1 = userMapper.findById(1L); // 执行SQLSqlSession sqlSession2 = sqlSessionFactory.openSession();UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);User user2 = userMapper2.findById(1L); // 再次执行SQLSystem.out.println("不同Session查询结果相等:" + user1.equals(user2)); // trueSystem.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); // 重新执行SQLSystem.out.println("更新后是否同一对象:" + (user3 == user4)); // false// 场景3:手动清空缓存System.out.println("=== 场景3:手动清空缓存 ===");User user5 = userMapper.findById(1L); // 从缓存获取sqlSession.clearCache(); // 手动清空缓存User user6 = userMapper.findById(1L); // 重新执行SQLSystem.out.println("清空缓存后是否同一对象:" + (user5 == user6)); // falsesqlSession.close();sqlSession2.close();
}
2.3 一级缓存的配置
<!-- mybatis-config.xml中的一级缓存配置 -->
<settings><!-- 本地缓存机制,SESSION或STATEMENT --><setting name="localCacheScope" value="SESSION"/>
</settings>
localCacheScope配置说明:
- SESSION(默认):缓存在整个SqlSession期间有效
- STATEMENT:缓存仅在语句执行期间有效,执行完立即清空
3. 二级缓存详解
3.1 二级缓存的特点
二级缓存是Mapper级别的缓存,需要手动配置:
<!-- 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 二级缓存配置详解
<!-- 详细的二级缓存配置 -->
<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 二级缓存使用示例
/*** 二级缓存演示*/
@Test
public void testSecondLevelCache() {// 第一个SqlSessionSqlSession 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,一级缓存清空,二级缓存保留// 第二个SqlSessionSqlSession sqlSession2 = sqlSessionFactory.openSession();UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);System.out.println("=== 第二个SqlSession查询 ===");User user2 = userMapper2.findById(1L); // 从二级缓存获取,不执行SQLSystem.out.println("用户信息:" + user2);System.out.println("对象内容相等:" + user1.equals(user2)); // trueSystem.out.println("是否同一对象:" + (user1 == user2)); // false(因为是拷贝)sqlSession2.close();
}
3.4 自定义缓存实现
/*** 自定义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);}@Overridepublic String getId() {return this.id;}@Overridepublic void putObject(Object key, Object value) {String redisKey = generateKey(key);redisTemplate.opsForValue().set(redisKey, value, 30, TimeUnit.MINUTES);}@Overridepublic Object getObject(Object key) {String redisKey = generateKey(key);return redisTemplate.opsForValue().get(redisKey);}@Overridepublic Object removeObject(Object key) {String redisKey = generateKey(key);Object value = redisTemplate.opsForValue().get(redisKey);redisTemplate.delete(redisKey);return value;}@Overridepublic void clear() {Set<String> keys = redisTemplate.keys(id + ":*");if (keys != null && !keys.isEmpty()) {redisTemplate.delete(keys);}}@Overridepublic int getSize() {Set<String> keys = redisTemplate.keys(id + ":*");return keys != null ? keys.size() : 0;}private String generateKey(Object key) {return id + ":" + key.toString();}
}
<!-- 使用自定义缓存 -->
<cache type="com.example.cache.RedisCache"><property name="timeout" value="1800"/>
</cache>
4. 缓存失效和更新策略
4.1 缓存失效场景
/*** 缓存失效场景演示*/
@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. 再次查询,重新执行SQLUser user2 = userMapper.findById(1L);System.out.println("更新后查询:" + user2);sqlSession.close();
}
4.2 缓存更新策略
<!-- 配置缓存刷新策略 -->
<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缓存管理
<!-- 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_idWHERE 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优化
<!-- 优化前: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_idWHERE 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_descriptionFROM users uLEFT JOIN user_roles ur ON u.id = ur.user_idLEFT JOIN roles r ON ur.role_id = r.id
</select>
5.2 分页查询优化
<!-- 物理分页 -->
<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 DESCLIMIT #{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 批量操作优化
<!-- 批量插入优化 -->
<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 usersSET username = #{user.username}, email = #{user.email}WHERE id = #{user.id}</foreach>
</update>
5.4 连接池优化
<!-- 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 缓存统计信息
/*** 缓存统计工具类*/
@Component
public class CacheStatistics {@Autowiredprivate 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 缓存调试配置
<!-- 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,以及事务管理的最佳实践。
相关文章:
- 上一篇:SQL映射文件与动态SQL
- 下一篇:MyBatis与Spring集成
- 返回目录