Mybatis Cache缓存机制

剖析两个经典的缓存策略:最近最少被使用(LRU)和软引用缓存

LRU,Mybatis巧妙的使用了LinkedHashMap.removeEldestEntry,此方法是一个受保护的方法,继承并重写这个方法,在方法中eldestKey持有被回收的对象,在做相关操作的使用轮训eldestKey,发现对象存在的时候,回收缓存容器中的对象。

 

public class LruCache implements Cache {

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }

  public String getId() {
    return delegate.getId();
  }

  public int getSize() {
      return delegate.getSize();
  }

  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  public Object getObject(Object key) {
    keyMap.get(key); //touch
    return delegate.getObject(key);

  }

  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  public ReadWriteLock getReadWriteLock() {
    return delegate.getReadWriteLock();
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

软引用缓存,个人认为比较高效的缓存,也是结合了Java垃圾回收的特点,也就是说如果对象不被引用,一旦开始垃圾回收,则该缓存对象会被回收。Mybatis使用了ReferenceQueue,会根据垃圾回收结果,在操作缓存是,回收缓存容器对象。

 

public class SoftCache implements Cache {
  private final LinkedList hardLinksToAvoidGarbageCollection;
  private final ReferenceQueue queueOfGarbageCollectedEntries;
  private final Cache delegate;
  private int numberOfHardLinks;

  public SoftCache(Cache delegate) {
    this.delegate = delegate;
    this.numberOfHardLinks = 256;
    this.hardLinksToAvoidGarbageCollection = new LinkedList();
    this.queueOfGarbageCollectedEntries = new ReferenceQueue();
  }

  public String getId() {
    return delegate.getId();
  }

  public int getSize() {
    removeGarbageCollectedItems();
    return delegate.getSize();
  }

  public void setSize(int size) {
    this.numberOfHardLinks = size;
  }

  public void putObject(Object key, Object value) {
    removeGarbageCollectedItems();
    delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
  }

  public Object getObject(Object key) {
    Object result = null;
    SoftReference softReference = (SoftReference) delegate.getObject(key);
    if (softReference != null) {
      result = softReference.get();
      if (result == null) {
        delegate.removeObject(key);
      } else {
        hardLinksToAvoidGarbageCollection.addFirst(result);
        if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
          hardLinksToAvoidGarbageCollection.removeLast();
        }
      }
    }
    return result;
  }

  public Object removeObject(Object key) {
    removeGarbageCollectedItems();
    return delegate.removeObject(key);
  }

  public void clear() {
    hardLinksToAvoidGarbageCollection.clear();
    removeGarbageCollectedItems();
    delegate.clear();
  }

  public ReadWriteLock getReadWriteLock() {
    return delegate.getReadWriteLock();
  }

  private void removeGarbageCollectedItems() {
    SoftEntry sv;
    while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
      delegate.removeObject(sv.key);
    }
  }

  private static class SoftEntry extends SoftReference {
    private final Object key;

    private SoftEntry(Object key, Object value, ReferenceQueue garbageCollectionQueue) {
      super(value, garbageCollectionQueue);
      this.key = key;
    }
  }

}
<think>好的,我现在需要详细介绍一下MyBatis缓存机制。首先,我需要回顾一下MyBatis的基本架构和缓存的作用。MyBatis是一个优秀的持久层框架,它封装了JDBC操作,简化了数据库交互。缓存机制在这里主要是为了提高性能,减少数据库查询次数,尤其是在频繁读取相同数据的场景下。 接下来,我需要明确MyBatis缓存分为一级缓存和二级缓存。一级缓存是SqlSession级别的,而二级缓存是Mapper级别的。用户可能希望了解这两者的区别、如何工作、如何配置以及使用时的注意事项。 首先是一级缓存。根据之前的了解,一级缓存默认是开启的,它存在于SqlSession的生命周期中。当同一个SqlSession执行相同的SQL查询时,MyBatis会从缓存中直接获取结果,而不是再次访问数据库。不过,如果执行了增删改操作,或者调用了commit、close等方法,缓存会被清空。需要解释这个过程,并举例说明。 然后是二级缓存。二级缓存需要手动配置,它是跨SqlSession的,属于Mapper级别的缓存。不同的SqlSession如果属于同一个Mapper,可以共享缓存数据。这里需要注意二级缓存的配置步骤,包括在MyBatis配置文件中启用,以及在Mapper XML文件中配置<cache>标签。同时,实体类需要实现Serializable接口,否则可能会出现序列化错误。 还需要提到二级缓存的一些高级配置,比如eviction策略(LRU、FIFO等)、flushInterval(刷新间隔)、size(缓存大小)等。此外,二级缓存可能存在脏读的问题,尤其是在分布式环境中,需要谨慎使用。 接下来是缓存的执行顺序。当查询发生时,MyBatis会先查看二级缓存,再查看一级缓存,如果都没有,才会查询数据库。更新操作会清空相关缓存,以保证数据一致性。这一点需要详细说明,避免用户在使用时出现数据不一致的情况。 此外,用户可能关心如何控制缓存的开启和关闭,比如在特定查询中跳过缓存,或者调整缓存的策略。比如,在select标签中设置useCache属性,或者在insert、update、delete标签中设置flushCache属性。 可能还需要提到第三方缓存集成,比如EhcacheMyBatis支持通过实现Cache接口来整合其他缓存框架,提升缓存性能和功能。这对高级用户来说可能是一个有用的扩展点。 在使用缓存时,常见的问题包括缓存穿透、缓存雪崩和缓存击穿,不过这些更多是分布式缓存中的问题,MyBatis缓存机制相对简单,但了解这些概念有助于用户更好地设计缓存策略。 最后,需要总结缓存的优缺点,适用的场景,以及在实际开发中的最佳实践。比如,一级缓存适合在同一个会话中重复查询,而二级缓存适合跨会话的只读或读多写少的场景。同时,要注意事务管理和缓存一致性的问题。 在整理这些信息时,需要确保逻辑清晰,结构合理,分点说明,便于用户理解。同时,要检查技术细节的准确性,避免错误的信息。如果有不确定的地方,比如二级缓存的默认行为或某些配置属性的具体含义,可能需要查阅官方文档或权威资料进行确认。 另外,用户可能希望看到具体的配置示例和代码片段,这有助于他们更好地应用这些知识。例如,展示如何在mybatis-config.xml中启用二级缓存,如何在Mapper XML中配置<cache>标签,以及如何在实体类中实现Serializable接口。 还需要注意,不同版本的MyBatis可能在缓存机制上有细微差异,特别是如果用户使用的是较新或较旧的版本,可能需要指出版本相关的注意事项。例如,某些属性可能在特定版本中被弃用或引入。 在回答中,应该避免过于技术化的术语,用通俗易懂的语言解释概念。同时,通过对比一级缓存和二级缓存的异同,帮助用户形成清晰的认识。可能使用表格来总结两者的区别,这样信息呈现更直观。 最后,要提醒用户在使用缓存时需要注意的问题,比如数据一致性、缓存失效策略、并发环境下的线程安全等,以及如何根据实际需求选择合适的缓存策略和配置。</think>--- ### MyBatis缓存机制详解 MyBatis缓存机制通过减少数据库查询次数提升性能,分为**一级缓存**和**二级缓存**。以下是详细说明: --- ### 一、一级缓存(本地缓存) #### 1. 作用范围 - **SqlSession级别**:仅在同一个SqlSession内有效。 - **默认开启**:无需配置。 #### 2. 工作流程 1. 首次查询时,结果存入SqlSession的缓存。 2. 后续**相同查询**(相同SQL、参数、Mapper方法)直接从缓存读取。 3. 以下操作会**清空缓存**: ```java sqlSession.commit(); // 提交事务 sqlSession.close(); // 关闭会话 sqlSession.clearCache(); // 手动清空缓存 userMapper.update(user); // 执行增删改操作 ``` #### 3. 示例 ```java try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); // 第一次查询(访问数据库) User user1 = mapper.selectById(1); // 第二次查询(命中缓存) User user2 = mapper.selectById(1); // 执行更新操作(清空缓存) mapper.updateName(1, "NewName"); // 第三次查询(重新访问数据库) User user3 = mapper.selectById(1); } ``` --- ### 二、二级缓存(全局缓存) #### 1. 作用范围 - **Mapper/Namespace级别**:跨SqlSession共享。 - **需手动开启**:需配置且实体类必须实现`Serializable`接口。 #### 2. 配置步骤 ##### (1) MyBatis主配置中启用缓存 ```xml <settings> <setting name="cacheEnabled" value="true"/> </settings> ``` ##### (2) Mapper XML中声明缓存策略 ```xml <mapper namespace="com.example.UserMapper"> <!-- 启用二级缓存 --> <cache eviction="LRU" <!-- 淘汰策略(LRU/FIFO/SOFT/WEAK) --> flushInterval="60000" <!-- 自动刷新间隔(毫秒) --> size="1024" <!-- 最大缓存对象数 --> readOnly="true"/> <!-- 是否只读 --> <select id="selectById" resultType="User" useCache="true"> SELECT * FROM user WHERE id = #{id} </select> </mapper> ``` ##### (3) 实体类实现序列化接口 ```java public class User implements Serializable { private static final long serialVersionUID = 1L; // 其他字段和方法... } ``` #### 3. 工作流程 1. 数据首次查询后存入二级缓存。 2. 其他SqlSession执行**相同Mapper查询**时,若缓存未过期,直接返回数据。 3. 执行**增删改操作**时,所属Mapper的缓存会被清空。 --- ### 三、缓存执行顺序 1. 查询请求到达后,优先检查**二级缓存**。 2. 若二级缓存未命中,检查**一级缓存**。 3. 若均未命中,执行数据库查询,结果依次存入一级和二级缓存。 --- ### 四、缓存对比(表格) | **特性** | 一级缓存 | 二级缓存 | |------------------|----------------------------------|----------------------------------| | 作用范围 | SqlSession内部 | 跨SqlSession(同Mapper) | | 默认状态 | 开启 | 关闭(需手动配置) | | 存储位置 | 内存(JVM堆) | 内存(可扩展为Redis/Ehcache等) | | 数据共享 | 否 | 是 | | 失效条件 | SqlSession关闭或执行更新操作 | 执行更新操作或配置的过期时间触发 | --- ### 五、高级特性与注意事项 #### 1. 缓存淘汰策略(eviction) - `LRU`(默认):最近最少使用 - `FIFO`:先进先出 - `SOFT`:软引用(GC时可能被回收) - `WEAK`:弱引用(GC时立即回收) #### 2. 缓存穿透防护 - 对空结果进行短时间缓存: ```xml <cache> <property name="cacheNullValues" value="true"/> </cache> ``` #### 3. 局部禁用缓存 - 在特定查询中关闭缓存: ```xml <select id="selectAll" resultType="User" useCache="false"> SELECT * FROM user </select> ``` #### 4. 强制刷新缓存 - 执行更新操作后自动刷新: ```xml <update id="updateUser" flushCache="true"> UPDATE user SET name=#{name} WHERE id=#{id} </update> ``` --- ### 六、常见问题与解决方案 | **问题** | **原因** | **解决方案** | |------------------------------|----------------------------------|----------------------------------| | 二级缓存未生效 | 实体类未实现`Serializable` | 添加`implements Serializable` | | 查询结果不一致 | 多表关联导致缓存未更新 | 在关联Mapper配置`<cache-ref>` | | 分布式环境下数据不同步 | 二级缓存仅限单机 | 集成Redis等分布式缓存 | | 内存溢出 | 缓存对象过多未限制 | 设置`size`属性或使用LRU策略 | --- ### 七、最佳实践 1. **一级缓存**:适合短会话、高频重复查询场景(如批量处理)。 2. **二级缓存**:适合读多写少、数据变更频率低的场景(如配置表)。 3. **生产环境建议**: - 优先使用Redis等外部缓存替代MyBatis内置二级缓存 - 对敏感数据关闭缓存(如账户余额) - 定期监控缓存命中率和内存使用情况 通过合理配置缓存,可提升系统性能约30%-70%,但需权衡数据一致性与性能需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值