当前位置: 首页 > java >正文

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查询,性能提升显著}
}

缓存的优势:

  1. 减少数据库访问 - 降低数据库负载
  2. 提高响应速度 - 内存访问比磁盘访问快得多
  3. 节省系统资源 - 减少网络传输和CPU消耗
  4. 提升用户体验 - 更快的页面加载速度

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();
}

一级缓存的特点:

  1. 默认开启 - 无法关闭,始终有效
  2. Session级别 - 同一个SqlSession内有效
  3. 自动管理 - 无需手动配置
  4. 生命周期短 - 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"/> <!-- 自定义缓存实现 -->

配置参数说明:

  1. eviction(回收策略)

    • LRU(默认):最近最少使用,移除最长时间不被使用的对象
    • FIFO:先进先出,按对象进入缓存的顺序来移除
    • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
    • WEAK:弱引用,更积极地基于垃圾收集器状态和弱引用规则移除对象
  2. flushInterval(刷新间隔)

    • 缓存多长时间清空一次,默认不清空
    • 单位:毫秒
  3. size(缓存大小)

    • 缓存存放多少元素,默认值是1024
  4. 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的缓存机制和性能优化:

  1. 一级缓存:SqlSession级别,默认开启,自动管理
  2. 二级缓存:Mapper级别,需要配置,支持自定义实现
  3. 缓存配置:回收策略、刷新间隔、大小限制等
  4. 性能优化:SQL优化、分页优化、批量操作、连接池调优
  5. 监控调试:缓存统计、日志配置、问题排查

掌握MyBatis缓存机制的关键点:

  • 理解一级缓存和二级缓存的区别和适用场景
  • 合理配置缓存参数,避免内存溢出
  • 注意缓存失效时机,保证数据一致性
  • 结合业务场景选择合适的缓存策略
  • 定期监控缓存效果,及时调优

🔗 下一篇预告

下一篇文章将介绍MyBatis与Spring集成,学习如何在Spring环境中使用MyBatis,以及事务管理的最佳实践。


相关文章:

  • 上一篇:SQL映射文件与动态SQL
  • 下一篇:MyBatis与Spring集成
  • 返回目录
http://www.xdnf.cn/news/18476.html

相关文章:

  • skywalking-agent与logback-spring.xml中的traceId自动关联的原理
  • 三,设计模式-抽象工厂模式
  • 深入解析TCP/UDP协议与网络编程
  • Leetcode—120. 三角形最小路径和【中等】(腾讯校招面试题)
  • SSM框架基础知识-Spring-Spring整合MyBatis
  • 基于SpringBoot+Vue框架的高校论坛系统 博客论坛系统 论坛小程序
  • 图神经网络分享系列-LINE(三)
  • Oracle SYS用户无法登录数据库-ORA-12162
  • Chrome和Edge如何开启暗黑模式
  • 本地部署DeepSeek实战
  • CS 创世 SD NAND 助力 T-BOX:破解智能汽车数字中枢的存储密码
  • 【UniApp打包鸿蒙APP全流程】如何配置并添加UniApp API所需的鸿蒙系统权限
  • 使用隧道(Tunnel)连接PostgreSQL数据库(解决防火墙问题)
  • 【Ansible】变量与敏感数据管理:Vault加密与Facts采集详解
  • 使用PPT进行科研绘图过程中常用的快捷键
  • Matplotlib 可视化大师系列(八):综合篇 - 在一张图中组合多种图表类型
  • Android 广告轮播全实现:图片与视频混合展示的完整方案
  • 基于ERNIE 4.5的多智能体协作的自动化视频舆情分析报告生成器
  • 图像处理中的伪影
  • git新建项目如何推送到远程仓库
  • C#_面向对象设计的艺术
  • Python 网络编程实战指南:从 TCP_UDP 到 Socket 编程
  • 基于LangChain + Milvus 实现RAG
  • Linux学习-网络编程2
  • Zynq开发实践(fpga高频使用的两个场景)
  • Elasticsearch Rails 实战全指南(elasticsearch-rails / elasticsearch-model)
  • VLLM部署gpt-oss-20b踩坑记录
  • chrome driver在Mac上运行时提示安全问题怎么解决
  • STM32 - Embedded IDE - GCC - 重定向printf到串口
  • jmeter