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;
}

mybatis 与 缓存

这是一个装饰者模式,在大部分情况下是直接转发调用的,在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接口,那么二级缓存用的是什么呢:

mybatis 与 缓存

事实上,这又是一个装饰者模式,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 = delegate.getObject(key);
if (value != null) {
hits++;
}
if (log.isDebugEnabled()) {
log.debug("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 = 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) {
// 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 = new StringBuilder().append(hashcode).append(':').append(checksum);
for (Object object : updateList) {
returnValue.append(':').append(object);
} return returnValue.toString();
} }

最后,附上一张缓存工作的时序图,TransactionCachingManager是二级缓存,LocalCache是一级缓存

mybatis 与 缓存

上一篇:利用WireShark进行DNS协议分析


下一篇:Emgu.CV(二)