首页 > 代码库 > mybatis 与 缓存
mybatis 与 缓存
首先从配置文件说起,有个cacheEnabled的配置项,当设置为true时(默认就是true),Session就会用一个CachingExecutor来包装我们的Executor实例:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这是一个装饰者模式,在大部分情况下是直接转发调用的,在update方法和query方法中分别根据mapper中statement的配置来对缓存进行读取和刷新
@Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); }
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
其中flushCacheIfRequired和ms.isUseCache都是在Mapper文件中配置的,用于刷新缓存和使用缓存
以上所说的缓存都是二级缓存,所谓二级缓存就是可以在查询结束后还能操作的全局缓存。
相比于全局缓存,那么就有局部缓存,也叫一级缓存,在mybatis中用LocalCache表示
Mybatis的局部缓存有两种作用域可以配置: SESSION和STATEMENT,会话作用域和语句作用域;本地缓存一直是开着的,在执行器内部会存放一个PerpetualCache类型的成员变量localCache来作为查询缓存,一个PerpetualCache类型的localOutputParameterCache成员来缓存存储过程的输出参数。如果是statement级别的缓存,那么在这个statement执行完成后就会清空缓存。如果执行更新操作会立即清空本地缓存。
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 // 如果是statement级别的缓存,那么在这个statement执行完成后就会清空缓存 clearLocalCache(); } } return list; } // 清空缓存 @Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } 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 { 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; } @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 执行更新操作会立即清空本地缓存 clearLocalCache(); return doUpdate(ms, parameter); }
我们可以看到,本地缓存使用的是PerpetualCache,它实现了Cache接口,那么二级缓存用的是什么呢:
事实上,这又是一个装饰者模式,Cache接口的唯一真正实现是PerpetualCache,其他的实现类都是对它的包装,比如LoggingCache:
public class LoggingCache implements Cache { private Log log; private Cache delegate; protected int requests = 0; // 访问数量 protected int hits = 0; // 命中数量 public LoggingCache(Cache delegate) { this.delegate = delegate; this.log = LogFactory.getLog(getId()); } ... omitted ... @Override public Object getObject(Object key) { requests++; final Object value = http://www.mamicode.com/delegate.getObject(key);"Cache Hit Ratio [" + getId() + "]: " + getHitRatio()); // 记录缓存命中率 } return value; } @Override public Object removeObject(Object key) { return delegate.removeObject(key); }
// 缓存命中率 private double getHitRatio() { return (double) hits / (double) requests; } }
LoggingCache是对Cache对象的一个包装,然后记录缓存命中率。
当然还有负责淘汰的包装器,比如 LruCache(默认的淘汰包装器):
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); } // 设置缓存大小,分配缓存存储空间,LinkedHashMap,移除最近最少使用的key的时候讲key交给eldestKey public void setSize(final int size) { keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { boolean tooBig = size() > size; if (tooBig) { eldestKey = eldest.getKey(); } return tooBig; } }; } @Override public void putObject(Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); } @Override public Object getObject(Object key) { keyMap.get(key); //touch一下,更新时间 return delegate.getObject(key); } @Override public void clear() { delegate.clear(); keyMap.clear(); } @Override public ReadWriteLock getReadWriteLock() { return null; } // 移除 eldestKey private void cycleKeyList(Object key) { keyMap.put(key, key); if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } } }
同样,这个缓存和淘汰缓存的实现都是可以在Mapper文件中指定的,可以自己写Cache实现类,然后配置在Mapper文件中
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
但是这些跟executor好像都没有什么关系,我们的CachingExecutor中是使用tcm成员对象来对缓存进行管理的,这个tcm实际上是TransactionalCacheManager的一个示例,TransactionalCacheManager类是对缓存的事务包装,细想,如果一个事务失败了,那个这个缓存该怎么搞呢,之前写了的缓存该怎么回滚?
在TransactionalCacheManager中是使用TransactionalCache来实现这一点的,他也是对Cache的一层包装,不过在putObject方法中,它并没有直接调用被包装cache对象的putObject,而是将键值对保存到自己的私有成员entriesToAddOnCommit中,待被调用commit方法时,才会将这些键值对通过调用被包装对象cache的putObject刷入真正的缓存:
private Map<Object, Object> entriesToAddOnCommit; //内部的键值对 public void putObject(Object key, Object object) { entriesToAddOnCommit.put(key, object); //put到内部的键值对 } public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset(); } public void rollback() { unlockMissedEntries(); reset(); } private void flushPendingEntries() { // 将内部的键值对刷入到 真正的cache中 for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } }
TransactionalCacheManager将Cache包装成TransactionalCache
private TransactionalCache getTransactionalCache(Cache cache) { TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; }
之后,来看看缓存key的计算,cacheKey是在BaseExecutor.createCacheKey方法中计算的:
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); // statementId cacheKey.update(rowBounds.getOffset()); // offset cacheKey.update(rowBounds.getLimit()); // limit cacheKey.update(boundSql.getSql()); // sql语句 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 = http://www.mamicode.com/boundSql.getAdditionalParameter(propertyName);"color: #ff0000">// 参数 } } if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); //数据库 } return cacheKey; }
cacheKey的计算是根据statementId, 分页情况,查询参数和数据库链接作为条件来计算的,CacheKey会根据这些条件来区分每一个CacheKey:
public class CacheKey implements Cloneable, Serializable { // hashCode扩展因子 private static final int DEFAULT_MULTIPLYER = 37; // hashcode初始值 private static final int DEFAULT_HASHCODE = 17; private int multiplier; 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 CacheKey(Object[] objects) { this(); updateAll(objects); } public int getUpdateCount() { return updateList.size(); } public void update(Object object) { if (object != null && object.getClass().isArray()) { int length = Array.getLength(object); for (int i = 0; i < length; i++) { Object element = Array.get(object, i); doUpdate(element); } } else { doUpdate(object); } } // 计算hashCode和校验和 private void doUpdate(Object object) { int baseHashCode = object == null ? 1 : object.hashCode(); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); } public void updateAll(Object[] objects) { for (Object o : objects) { update(o); } } // IMPORTANT @Override public boolean equals(Object object) { if (this == object) { return true; } if (!(object instanceof CacheKey)) { return false; } final CacheKey cacheKey = (CacheKey) object; // hash值不同,肯定不是同一个Cache if (hashcode != cacheKey.hashcode) { return false; } // 校验和不同,也不是相同的Cache if (checksum != cacheKey.checksum) { return false; } //参数数量不同 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 (thisObject == null) { if (thatObject != null) { return false; } } else { if (!thisObject.equals(thatObject)) { return false; } } } return true; }
// 根据条件产生cahceKey的字符串,效率较慢,给自定义缓存使用 @Override public String toString() { StringBuilder returnValue = http://www.mamicode.com/new StringBuilder().append(hashcode).append(‘:‘).append(checksum);>
最后,附上一张缓存工作的时序图,TransactionCachingManager是二级缓存,LocalCache是一级缓存
mybatis 与 缓存