MyBatis 缓存原理 - litter-fish/ReadSource GitHub Wiki

查询过程中缓存

Cache 缓存实现

关系说明:

MyBatis 在实现缓存模块的过程中,使用了装饰模式。在以上几种缓存实现类中,PerpetualCache 相当于装饰模式中的 ConcreteComponent。LruCache、SynchronizedCache 和 BlockingCache 等相当于装饰模式中的 ConcreteDecorator。

PerpetualCache

PerpetualCache 是一个具有基本功能的缓存类,内部使用了 HashMap 实现缓存功能

public class PerpetualCache implements Cache {
    public void putObject(Object key, Object value) {
        // 存储键值对到 HashMap
        cache.put(key, value);
    }

    public Object getObject(Object key) {
        // 查找缓存项
        return cache.get(key);
    }

    public Object removeObject(Object key) {
        // 移除缓存项
        return cache.remove(key);
    }
}

装饰类

LruCache,是一种具有 LRU 策略的缓存实现类, LruCache 的 keyMap 属性,该属性类型继承自 LinkedHashMap,并覆盖了 removeEldestEntry 方法。LinkedHashMap 可保持键值对的插入顺序,当插入一个新的键值对时,LinkedHashMap 内部的 tail 节点会指向最新插入的节点。head 节点则指向第一个被插入的键值对,也就是最久未被访问的那个键值对。

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 void setSize(final int size) {
    /*
     * 初始化 keyMap,注意,keyMap 的类型继承自 LinkedHashMap,
     * 并覆盖了 removeEldestEntry 方法
     *
     * 构造方法将 LinkedHashMap 的 accessOrder 属性设为 true,此时 LinkedHashMap 会维护键值对的访问顺序。
     * keyMap.get(key),刷新 key 对应的键值对在 LinkedHashMap 的位置,最少访问的放到队尾
     */
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      // 覆盖 LinkedHashMap 的 removeEldestEntry 方法
      @Override
      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);
  }

  private void cycleKeyList(Object key) {
    // 存储 key 到 keyMap 中
    keyMap.put(key, key);
    if (eldestKey != null) {
      // 从被装饰类中移除相应的缓存项
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

  public Object getObject(Object key) {
    // 刷新 key 在 keyMap 中的位置
    keyMap.get(key); //touch
    // 从被装饰类中获取相应缓存项
    return delegate.getObject(key);
  }

  public Object removeObject(Object key) {
    // 从被装饰类中移除相应的缓存项
    return delegate.removeObject(key);
  }

}

BlockingCache BlockingCache 实现了阻塞特性,该特性是基于 Java 重入锁实现的。同一时刻下,BlockingCache 仅允许一个线程访问指定 key 的缓存项,其他线程将会被阻塞住

public class BlockingCache implements Cache {

  private long timeout;
  private final Cache delegate;
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
  }

  public void putObject(Object key, Object value) {
    try {
      // 存储缓存项
      delegate.putObject(key, value);
    } finally {
      // 释放锁
      releaseLock(key);
    }
  }

  public Object getObject(Object key) {
    // 请求锁
    acquireLock(key);
    // 若缓存命中,则释放锁。需要注意的是,未命中则不释放锁
    Object value = delegate.getObject(key);
    if (value != null) {
      // 释放锁
      releaseLock(key);
    }
    return value;
  }

  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    // 释放锁
    releaseLock(key);
    return null;
  }

  private ReentrantLock getLockForKey(Object key) {
    ReentrantLock lock = new ReentrantLock();
    // 存储 <key, Lock> 键值对到 locks 中
    ReentrantLock previous = locks.putIfAbsent(key, lock);
    return previous == null ? lock : previous;
  }

  private void acquireLock(Object key) {
    Lock lock = getLockForKey(key);
    if (timeout > 0) {
      try {
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) {
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    } else {
      lock.lock();
    }
  }

  private void releaseLock(Object key) {
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) {
      lock.unlock();
    }
  }
}

CacheKey,构造缓存key

public class CacheKey implements Cloneable, Serializable {

  private static final int DEFAULT_MULTIPLYER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  // 乘子,默认为37
  private final int multiplier;
  // CacheKey 的 hashCode,综合了各种影响因子
  private int hashcode;
  // 校验和
  private long checksum;
  // 影响因子个数
  private int count;
  // 影响因子个数
  private List<Object> updateList;

  public CacheKey() {
    this.hashcode = DEFAULT_HASHCODE;
    this.multiplier = DEFAULT_MULTIPLYER;
    this.count = 0;
    this.updateList = new ArrayList<Object>();
  }

  /** 每当执行更新操作时,表示有新的影响因子参与计算 */
  public void update(Object object) {
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

    // 自增 count
    count++;
    // 计算校验和
    checksum += baseHashCode;
    // 更新 baseHashCode
    baseHashCode *= count;

    // 计算 hashCode
    hashcode = multiplier * hashcode + baseHashCode;

    // 保存影响因子
    updateList.add(object);
  }

  public boolean equals(Object object) {
    // 检测是否为同一个对象
    if (this == object) {
      return true;
    }
    // 检测 object 是否为 CacheKey
    if (!(object instanceof CacheKey)) {
      return false;
    }

    final CacheKey cacheKey = (CacheKey) object;

    // 检测 hashCode 是否相等
    if (hashcode != cacheKey.hashcode) {
      return false;
    }
    // 检测校验和是否相同
    if (checksum != cacheKey.checksum) {
      return false;
    }
    // 检测 count 是否相同
    if (count != cacheKey.count) {
      return false;
    }

    // 如果上面的检测都通过了,下面分别对每个影响因子进行比较
    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (!ArrayUtil.equals(thisObject, thatObject)) {
        return false;
      }
    }
    return true;
  }
}

一级缓存

一级缓存在 BaseExecutor 中被初始化

public abstract class BaseExecutor implements Executor {
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 首先根据传递的参数获取BoundSql对象,对于不同类型的SqlSource,对应的getBoundSql实现不同
        BoundSql boundSql = ms.getBoundSql(parameter);

        // 创建缓存key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

        // 委托给重载的query
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        // 省略部分代码
        List<E> list;
        try {
            queryStack++;
            // 从一级缓存中获取缓存项
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 只处理存储过程和函数调用的出参, 因为存储过程和函数的返回不是通过ResultMap而是ParameterMap来的,所以只要把缓存的非IN模式参数取出来设置到parameter对应的属性上即可
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 一级缓存未命中,则从数据库中查询
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        // 省略部分代码
        return list;
    }
}

通过MappedStatement 的 id 字段,SQL 语句,分页参数,运行时变量,Environment 的 id 字段等参与影响因子计算,可以区别不同查询请求

public abstract class BaseExecutor implements Executor {
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }

        // 创建 CacheKey 对象
        CacheKey cacheKey = new CacheKey();
        // 将 MappedStatement 的 id 作为影响因子进行计算
        cacheKey.update(ms.getId());
        // RowBounds 用于分页查询,下面将它的两个字段作为影响因子进行计算
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        // 获取 sql 语句,并进行计算
        cacheKey.update(boundSql.getSql());
        
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                Object value;
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                cacheKey.update(value);
            }
        }
        if (configuration.getEnvironment() != null) {
            // 获取 Environment id 遍历,并让其参与计算
            cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
    }
}

一级缓存未命中,查询数据库,并将结果写入一级缓存中

public abstract class BaseExecutor implements Executor {
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;

        // 向缓存中存储一个占位符
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 调用 doQuery 进行查询
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 移除占位符
            localCache.removeObject(key);
        }

        // 缓存查询结果
        localCache.putObject(key, list);

        // 如果是存储过程类型,则把查询参数放到本地出参缓存中, 所以第一次一定为空
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }
}

二级缓存

二级缓存构建在一级缓存上,当发起一个查询请求的时候 mybatis 会先去二级缓存中查找,如果未命中则继续在一级缓存中查找,如果还是未命中则查询数据库 一级缓存与 SqlSession 绑定,二级缓存与 Mapper 绑定。 二级缓存可以在多个命名空间中共享。这种情况下,会存在并发问题,因此需要针对性去处理。除了并发问题,二级缓存还存在事务问题

MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题,可以通过 SynchronizedCache 装饰类解决,该装饰类会在 Cache 实例构造期间被添加上 多个事务共用一个缓存实例,会导致脏读问题,使用 TransactionalCache 处理

二级缓存相关类图

访问二级缓存

public class CachingExecutor implements Executor {
    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取 BoundSql
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        // 创建 CacheKey
        CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
        // 调用重载方法
        return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
        // 从 MappedStatement 中获取 Cache,注意这里的 Cache 并非是在 CachingExecutor 中创建的
        Cache cache = ms.getCache();
        // 若映射文件中未配置缓存或参照缓存,此时 cache = null
        if (cache != null) {
            flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                ensureNoOutParams(ms, boundSql);
                // 如果二级缓存中找到了记录就直接返回,否则到DB查询后进行缓存
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                    // 若缓存未命中,则调用被装饰类的 query 方法
                    list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    tcm.putObject(cache, key, list); // issue #578 and #116
                }
                return list;
            }
        }
        // 调用被装饰类的 query 方法
        return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
}

事务缓存管理器的创建

/**事务缓存管理器 管理二级缓存对象Cache和TransactionCache */
public class TransactionalCacheManager {  
    // Cache 与 TransactionalCache 的映射关系表  
    private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
}

获取二级缓存

public class TransactionalCacheManager {
    public Object getObject(Cache cache, CacheKey key) {
        return getTransactionalCache(cache).getObject(key);
    }
    // 缓存装饰器,可以为 Cache 实例增加事务功能
    private TransactionalCache getTransactionalCache(Cache cache) {
        // 从映射表中获取 TransactionalCache
        TransactionalCache txCache = transactionalCaches.get(cache);
        if (txCache == null) {
            // TransactionalCache 也是一种装饰类,为 Cache 增加事务功能
            txCache = new TransactionalCache(cache);
            transactionalCaches.put(cache, txCache);
        }
        return txCache;
    }
}

创建缓存装饰器 TransactionalCache

public class TransactionalCache implements Cache {

  private final Cache delegate;
  private boolean clearOnCommit;
  // 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
  private final Map<Object, Object> entriesToAddOnCommit;
  // 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<Object, Object>();
    this.entriesMissedInCache = new HashSet<Object>();
  }
  // 省略其他代码
}

缓存装饰器 TransactionalCache 中获取对象

public class TransactionalCache implements Cache {

  // 省略其他代码
  public Object getObject(Object key) {
    // issue #116
    // 用到了装饰器模式,从PerpetualCache中取出数据
    Object object = delegate.getObject(key);
    if (object == null) {
      // 缓存未命中,则将 key 存入到 entriesMissedInCache 中
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }
  // 省略其他代码
}

缓存查询结果,此步骤只会将需要缓存的数据暂存在 entriesToAddOnCommit 中当执行事务提交及 commit 的时候才会真正将缓存写入二级缓存对象中

public class TransactionalCacheManager {
    public void putObject(Cache cache, CacheKey key, Object value) {
        getTransactionalCache(cache).putObject(key, value);
    }
}
public class TransactionalCache implements Cache {

  // 省略其他代码
  public void putObject(Object key, Object object) {
    // 将键值对存入到 entriesToAddOnCommit 中,而非 delegate 缓存中
    entriesToAddOnCommit.put(key, object);
  }
  // 省略其他代码
}
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
    private class SqlSessionInterceptor implements InvocationHandler {
        public SqlSessionInterceptor() {
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
            if (sqlSession != null) {
                try {
                    // 执行select、update、delete等
                    return method.invoke(sqlSession, args);
                } catch (Throwable t) {
                    throw ExceptionUtil.unwrapThrowable(t);
                }
            } else {
                final SqlSession autoSqlSession = openSession();
                try {
                    final Object result = method.invoke(autoSqlSession, args);
                    // 事务提交
                    autoSqlSession.commit();
                    return result;
                } catch (Throwable t) {
                    autoSqlSession.rollback();
                    throw ExceptionUtil.unwrapThrowable(t);
                } finally {
                    autoSqlSession.close();
                }
            }
        }
    }
}

执行事务提交时将 TransactionalCache 中暂存的缓存对象存入二级缓存中

public class TransactionalCache implements Cache {

  // 省略其他代码
  public void commit() {
    // 根据 clearOnCommit 的值决定是否清空 delegate
    if (clearOnCommit) {
      delegate.clear();
    }
    // 刷新未缓存的结果到 delegate 缓存中
    flushPendingEntries();
    // 重置 entriesToAddOnCommit 和 entriesMissedInCache
    reset();
  }

  private void flushPendingEntries() {
    // 把之前存放到 entriesToAddOnCommit 中的数据提交到二级缓存中,具体的说是存放到PerpetualCache类的一个HashMap中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      // 将 entriesToAddOnCommit 中的内容转存到 delegate 中
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        // 存入空值
        delegate.putObject(entry, null);
      }
    }
  }

  private void reset() {
    clearOnCommit = false;
    // 清空集合
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  public void clear() {
    clearOnCommit = true;
    // 清空 entriesToAddOnCommit,但不清空 delegate 缓存
    entriesToAddOnCommit.clear();
  }
  // 省略其他代码
}

事务回滚

public class TransactionalCacheManager {
    public void rollback() {
        for (TransactionalCache txCache : transactionalCaches.values()) {
            txCache.rollback();
        }
    }
}

public class TransactionalCache implements Cache {

  // 省略其他代码
  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        // 调用 removeObject 进行解锁
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
            + "Consider upgrading your cache adapter to the latest version.  Cause: " + e);
      }
    }
  }

  private void reset() {
    clearOnCommit = false;
    // 清空集合
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  // 省略其他代码
}
⚠️ **GitHub.com Fallback** ⚠️