Mybatis 缓存是如何工作的? 缓存的 key 是如何生成的? 缓存的淘汰策略有哪些?

MyBatis 缓存是如何工作的?

MyBatis 的缓存工作机制主要围绕一级缓存和二级缓存展开:

  1. 一级缓存 (SqlSession 级别):

    • 开启与作用域: 默认开启,作用域是 SqlSession。每个 SqlSession 内部维护一个简单的 HashMap 作为缓存。
    • 工作流程:
      • SqlSession 执行一个查询时,它会先根据特定的规则生成一个缓存 Key
      • 使用这个 Key 尝试在当前 SqlSession 的一级缓存 (HashMap) 中查找结果。
      • 缓存命中 (Hit): 如果找到了对应的结果对象(引用),则直接返回该对象引用,不再查询数据库。
      • 缓存未命中 (Miss): 如果缓存中没有找到,则执行数据库查询。
      • 查询数据库后,将获取到的结果对象(引用)存入当前 SqlSession 的一级缓存中,Key 就是之前生成的那个。
    • 生命周期与失效:
      • 一级缓存的生命周期与 SqlSession 完全一致。当 SqlSession 关闭 (close()) 时,一级缓存被清空。
      • 执行 commit() 操作时,会清空一级缓存(即使没有修改操作,因为无法确定是否有其他会话修改了数据)。
      • 执行任何 INSERT, UPDATE, DELETE 操作时,会清空一级缓存,以保证缓存数据的准确性。
      • 手动调用 sqlSession.clearCache() 会清空一级缓存。
  2. 二级缓存 (Mapper Namespace 级别):

    • 开启与作用域: 默认关闭,需要显式配置开启。作用域是 Mapper 的 Namespace,可以被多个 SqlSession 共享。通常使用一个实现了 Cache 接口的类来存储(默认是 PerpetualCache,内部也是 HashMap,但可配置为 Ehcache, Redis 等)。
    • 工作流程:
      • 当一个 SqlSession 执行查询时,如果该 Mapper 配置了二级缓存 ( 标签) 并且全局开关已打开 (cacheEnabled=true):
        • 它会先尝试根据规则生成的 Key二级缓存中查找。
        • 二级缓存命中: 如果找到,并且缓存配置为 readOnly="false"(默认),则返回结果对象的反序列化副本;如果配置为 readOnly="true",则返回对象引用(性能高但有线程安全风险)。
        • 二级缓存未命中: 继续查找一级缓存
        • 一级缓存命中: 返回一级缓存中的对象引用。
        • 一级缓存也未命中: 执行数据库查询。
        • 查询结果存入一级缓存
      • 数据进入二级缓存:SqlSession 提交 (commit()) 或关闭 (close()) 时,一级缓存中与配置了二级缓存的 Mapper 相关的数据,会被刷新(放入)到该 Mapper Namespace 对应的二级缓存中(通常是序列化存储)。
    • 生命周期与失效:
      • 二级缓存的生命周期与应用程序(SqlSessionFactory)相关,除非被策略性清除或手动清除。
      • 当同一个 Namespace 下执行了任何 INSERT, UPDATE, DELETE 操作(且 flushCache="true",这是默认值)时,该 Namespace二级缓存会被清空
      • 可以通过 标签的 flushInterval 属性设置定时清空。
      • 缓存达到最大容量 (size 属性) 时,会根据 eviction 策略进行淘汰。
      • 可以手动获取 Cache 对象并调用 clear() 方法。

缓存的 Key 是如何生成的?

缓存的 Key 对于确保缓存的正确性至关重要。MyBatis 需要确保只有在完全相同的查询条件下才能命中缓存。缓存 KeyCacheKey 对象)的生成通常由以下几个部分组成,以保证其唯一性:

  1. MappedStatement 的 ID: 即 Mapper 接口的全限定名 + 方法名(或 XML 中的