前言在日常开发中MyBatis 是我们接触最多的持久层框架之一它以简化 JDBC 操作、灵活的 SQL 编写能力成为了很多Java 开发者的首选而缓存作为MyBatis性能优化的核心手段更是学习的核心重点一、MyBatis执行流程先来了解一下MyBatis的执行流程再来看缓存到底怎么优化性能的执行流程可以分为两大阶段初始化阶段、执行阶段而缓存的核心载体sqlsession就是在执行阶段诞生的1.初始化阶段构建全局配置与工厂项目启动时MyBatis 会一次性完成配置加载与核心对象构建读取mybatis-config.xml核心配置文件以及所有 Mapper XML / 注解接口将所有配置数据源、事务、映射关系、SQL 语句解析并封装为全局唯一的Configuration对象通过SqlSessionFactoryBuilder构建SqlSessionFactory这是一个全局单例的工厂对象负责创建SqlSession。2.执行阶段一次请求的完整链路每次业务请求到来MyBatis 都会走一遍以下流程而缓存的作用就藏在其中通过SqlSessionFactory.openSession()创建SqlSession对象这是与数据库交互的核心会话从SqlSession中获取 Mapper 接口的动态代理对象调用 Mapper 方法时代理对象会找到对应的 SQL 语句交由Executor执行器处理Executor会先尝试从缓存中获取数据未命中则执行 JDBC 查询并将结果存入缓存将查询结果映射为 Java 对象返回给业务层最终关闭SqlSession。从流程中可以看出缓存是SqlSession和Executor为了减少数据库访问而内置的优化机制而 MyBatis 的缓存体系也正是围绕SqlSession展开的。整体大致流程如下二、SqlSessionMyBatis缓存的核心载体从上面图里可以看出sqlsession相当于mybatis和数据库的桥梁不仅如此还是缓存的相关载体1.SqlSession的核心定位sqlsession可以理解为一次数据库会话一次请求的专属操作对象。它封装了数据库连接、事务控制提交 / 回滚提供了增删改查的 API也能获取 Mapper 代理对象持有默认开启的一级缓存本地缓存这是 MyBatis 最基础的缓存实现。2. SqlSession 的生命周期为了好理解缓存失效我们先来搞懂sqlsession的生命周期SqlSessionFactory全局单例与应用同生命周期不持有任何缓存SqlSession线程不安全生命周期为一次请求 / 一次方法调用每次请求都会创建新的SqlSession请求结束后关闭销毁一级缓存是SqlSession级别的因此它的生命周期和SqlSession完全绑定会话关闭缓存也随之销毁。理解了这一点再去看 MyBatis 的缓存体系就会豁然开朗。三、缓存1.什么是缓存缓存是一种临时数据存储机制它将频繁访问、查询成本高的数据存储在读取速度更快的介质如内存中当再次请求相同数据时直接从缓存中读取避免重复访问底层数据源如数据库从而提升系统响应速度降低数据库压力。简单来说缓存就是 “把常用数据存起来下次直接用不用再查数据库”。2.为什么使用缓存缓存的核心价值在于解决数据库性能瓶颈降低数据库压力重复查询直接命中缓存减少数据库访问次数提升系统响应速度内存读取速度远快于数据库磁盘 IO用户体验更好减少网络开销避免重复的数据库连接与数据传输降低网络负载。3. 什么样的数据适合使用缓存不是所有数据都适合缓存使用缓存前必须判断数据的特性读多写少比如字典表、配置表、用户信息等查询频率远高于修改频率数据一致性要求不高短时间内数据不一致不影响业务如商品库存可接受几秒延迟数据不频繁变动频繁更新的数据会导致缓存频繁失效反而增加开销非核心业务数据核心交易数据如订单金额不建议使用缓存避免数据不一致导致业务错误。四、MyBatis缓存体系一级缓存二级缓存MyBatis 内置了两级缓存默认开启一级缓存二级缓存需要手动配置。我们先从最基础的一级缓存讲起。1. 一级缓存本地缓存一级缓存是 MyBatis 最基础的缓存也是默认开启、无法关闭的缓存机制。1什么是一级缓存一级缓存也叫本地缓存是SqlSession级别的缓存它的核心特性如下作用域同一个SqlSession会话内存储介质内存底层是HashMap默认状态强制开启无法关闭缓存 Key由Statement ID SQL 语句 参数 分页条件组成确保只有完全相同的查询才能命中缓存。实际上就是存储一个对象一旦条件改变那么这个缓存就会失效2测试例子可以通过一段代码来理解“本质是存储一个对象”这句话第二次查询直接从一级缓存中获取了数据而不是访问数据库public class FirstLevelCacheTest { public static void main(String[] args) { SqlSession sqlSession sqlSessionFactory.openSession(); UserMapper mapper sqlSession.getMapper(UserMapper.class); // 第一次查询访问数据库结果存入一级缓存 User user1 mapper.selectById(1); System.out.println(user1); // 第二次查询同一SqlSession、相同SQL直接命中缓存不访问数据库 User user2 mapper.selectById(1); System.out.println(user2); // 验证两个对象是同一个内存地址相同 System.out.println(user1 user2); // 输出 true sqlSession.close(); } }3缓存失效刚刚提到了只要改变条件这个缓存就会失效那缓存失效还有哪些场景① SqlSession执行写操作并提交事务当同一个SqlSession执行insert/update/delete操作并调用commit()时会清空该会话的一级缓存避免脏数据。② 手动调用clearCache ( ) 调用sqlSession.clearCache()会直接清空当前会话的一级缓存。③ 关闭SqlSession会话SqlSession关闭后缓存对象被销毁缓存自然失效④ 不同的SqlSession会话一级缓存是会话级别的不同SqlSession的缓存相互隔离无法共享。⑤ 查询条件发生变化两次查询的 SQL、参数、分页条件不同缓存 Key 不匹配无法命中缓存。2.二级缓存全局缓存一级缓存只能在同一个SqlSession中生效无法跨会话共享。为了解决这个问题MyBatis 提供了二级缓存它是Mapper命名空间级别的缓存跨SqlSession共享。如果说一级缓存存的是对象那么二级缓存村的就是数据可以存很多条二级缓存开启的条件二级缓存默认关闭需要同时满足以下条件才能生效① 全局配置中开启二级缓存在mybatis-config.xml中设置cacheEnabledtrue默认就是 true可省略settings setting namecacheEnabled valuetrue/ /settings② 在对应的Mapper xml中配置cache/标签该标签意味着当前命名空间下所有查询都是用二级缓存!-- UserMapper.xml 中开启二级缓存 -- mapper namespacecom.example.mapper.UserMapper cache/ select idselectById resultTypecom.example.pojo.User select * from user where id #{id} /select /mapper③ 序列化接口二级缓存会将数据序列化后存储因此实体类要实现序列化接口implement Serializable④ SqlSession必须提交或关闭二级缓存的数据不会在查询后立即写入只有当SqlSession调用commit()或close()时才会将数据写入二级缓存。3.缓存查询顺序MyBatis 执行查询操作时会按照以下顺序查找数据1先查二级缓存根据 Mapper 命名空间 缓存 Key查找全局缓存命中直接返回数据未命中进入下一步。2再查一级缓存在当前SqlSession会话内根据缓存 Key 查找本地缓存命中直接返回数据未命中进入下一步。3最后查询数据库执行 JDBC 查询获取数据后存入当前会话的一级缓存会话提交 / 关闭后写入二级缓存。