解析基于Mybatis 3.4.4 Release,近几个版本应该都适用。
Mybatis二级缓存总开关默认是开启的。
public class Configuration {
......
protected boolean cacheEnabled = true;
}
这样SqlSessionFactory在生成SqlSession时,就会把原来的Eexcutor包装成一个CachingExecutor:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 生成Executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
public class Configuration {
protected boolean cacheEnabled = true;
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) { // 包装成 CachingExecutor
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
下面我们看看CachingExecutor在查询时是如何使用缓存的:
// CachingExecutor 实现Executor 接口,同时又持有一个Executor 的实例,大多数方法最终还是要转发到delegate去执行
// CachingExecutor 对 delegate 做了装饰,或者在查询时做了静态代理,如果有缓存则直接返回,没有就用delegate去执行
public class CachingExecutor implements Executor {
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager();
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache(); // 获取MappedStatement 上的缓存对象
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
// 通过CacheKey 作为key去缓存里搜索
List list = (List) tcm.getObject(cache, key);
if (list == null) {
// 如果缓存value不存在,那么借助delegate执行查询,然后存入Cache,这样相同的查询下次就能复用此结果了
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 是否要刷新缓存
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
}
以上代码有两点关键:
我们先看第一点吧,解析Cache对象的实例化过程。
Mybatis在构建SqlSessionFactory时,会解析指定路径下的所有Mapper.xml文件:
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache")); //解析cache节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
}
以下就是一个< cache>节点的定义形式:
type="PERPETUAL" eviction="LRU" size="60" blocking="false" flushInterval="24" readOnly="false"/>
下面看< cache>节点的解析代码:
public class XMLMapperBuilder extends BaseBuilder {
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);
}
}
}
在解析< cache>节点时,其元素大部分都有默认值,通过typeAliasRegistry将值转换为想要的类型:
public class Configuration {
public Configuration() {
......
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
......
}
}
Configuration 在实例化时就为typeAliasRegistry注册了很多别名,在解析Mapper.xml时就能通过别名来获取到真实的Class类型。
接下来看Cache对象的创建过程:
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private Cache currentCache;
public Cache useNewCache(Class extends Cache> typeClass,
Class extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 每个Cache都是和currentNamespace紧密关联的,也就是Cache和Mapper.xml里面的namespace元素关联的,也就是每个Mapper匹配一个Cache
// 也就是说Mapper.xml内元素解析成成的MappedStatement共用一个Cache对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build(); // 开始通过建造者构建Cache
configuration.addCache(cache); // 将Cache对象添加到Configuration中
currentCache = cache; //设置currentCache = cache
return cache;
}
}
已经将所有Cache所有相关的数据注入到CacheBuilder中了,接下来看其build( )方法:
public class CacheBuilder {
private String id;
private Class extends Cache> implementation;
private List<Class extends Cache>> decorators;
private Integer size;
private Long clearInterval;
private boolean readWrite;
private Properties properties;
private boolean blocking;
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class extends Cache> decorator : decorators) {
// 将PerpetualCache外层装饰上 < cache> 内 "eviction" 指定的缓存淘汰策略,可以是 "FIFO","LRU","SOFT","WEAK" 中的一个
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
// 根据< cache> 的数据,在Cache对象外层再依次封装上相应的装饰类
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
// Cache的原始实现和原始装饰为空时使用默认值
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
// 根据< cache> 元素内的数据,生成相应的装饰类,封装原始的Cache对象
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) { // 加上定时清空策略, 时间单位时小时
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) { // 加上读写装饰,以二进制数组的形式缓存查询结果
cache = new SerializedCache(cache);
}
cache = new LoggingCache(cache); // 加上日志装饰,以便打印日志
cache = new SynchronizedCache(cache); // 加上同步装饰, 将非线程安全的方法都用 "synchronized" 修饰,确保线程安全
if (blocking) {
cache = new BlockingCache(cache); // 加上对同一key的阻塞访问装饰
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
}
以SynchronizedCache为例,看看其对原始Cache的装饰方式:
public class SynchronizedCache implements Cache {
private Cache delegate;
public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public synchronized int getSize() {
return delegate.getSize();
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public synchronized Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}
SynchronizedCache 将所有非线程安全的方法都加上了 “synchronized ”关键字,确保了缓存数据操作的线程安全性。因为这里的缓存大部分仅仅是对缓存容器的操作,不会占用太多资源和时间,所以性能还是可以接受的。
总结:这篇博客主要讲了 < cache> 元素的解析和实例化的过程,使用装饰者模式,根据< cache>元素内的数据定义,来生成相应的装饰者对象去装饰原始的PerpetualCache对象。