mybatis之缓存模块
mybatis中存在一级(默认开启)、二级缓存。缓存可以加快查询速度,减少连接db的次数。
cache模块属于mybatis的基础支持层。位于org.apache.ibatis.cache包下。
--------org.apache.ibatis.cache
-------------------------------decorators
-----------------------------------------BlockingCache
-----------------------------------------FifoCache
-----------------------------------------LoggingCache
-----------------------------------------LruCache
-----------------------------------------ScheduledCache
-----------------------------------------SerializedCache
-----------------------------------------SoftCache
-----------------------------------------SynchronizedCache
-----------------------------------------TransactionalCache
-----------------------------------------WeakCache
-------------------------------impl
-----------------------------------PerpetualCache
------------------------------Cache
Cache
缓存的顶级接口
public interface Cache {
// 得到缓存的id
String getId();
// 插入缓存
void putObject(Object key, Object value);
// 查询到缓存
Object getObject(Object key);
// 删除缓存
Object removeObject(Object key);
//清理这个缓存实例
void clear();
// 这个方法可选,不是核心调用方法,查询缓存key的个数
int getSize();
//
ReadWriteLock getReadWriteLock();
}
PerpetualCache
缓存的基础实现(实现了基本的缓存功能,底层数据结构使用HashMap)
一级缓存是会话级别的缓存(SqlSession)。
public class PerpetualCache implements Cache {
private final String id;
// 存储缓存底层数据结构使用hashMap
private Map<Object, Object> cache = new HashMap<Object, Object>();
// 传入
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
// 插入缓存
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
//查询缓存
@Override
public Object getObject(Object key) {
return cache.get(key);
}
// 移除缓存
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
BlockingCache
BlockingCache是带有阻塞功能的缓存装饰器。
public class BlockingCache implements Cache {
//超时时间
private long timeout;
// 被装饰的缓存
private final Cache delegate;
// 每一个Key拥有一把可重入锁
private final ConcurrentHashMap<Object, ReentrantLock> locks;
//构造器,装饰其他cache对象
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
// 插入Key的缓存,插入完毕同时释放锁
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
releaseLock(key);
}
}
// 查询key的缓存
@Override
public Object getObject(Object key) {
//获取到key的锁
acquireLock(key);
// 查询key缓存的值
Object value = delegate.getObject(key);
//如果value不为null,则释放锁,返回值,否则一直持有锁,知道key有值插入到缓存
if (value != null) {
releaseLock(key);
}
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
//查询Key持有的锁,如果没有则创建新锁
private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();
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();
}
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
线程A第一次查询key,结果不存在缓存,A持有key的锁; 同时线程B查询key,此时线程B被阻塞,A去数据查询获取到数据缓存起来释放key的锁,B被唤醒继续从缓存查询。
BlockingCache会装饰PerpetualCache缓存。
装饰者模式
从上面缓存的包结构上我们可以看到,Cache是顶层接口,PerpetualCache是基本实现,decorators下的各个类是不同功能的缓存器,通过装饰者模式来实现不同功能的组合。
//顶测接口
public interface Cache{
Object getObject(Object key);
}
//基本实现类
public class PerpetualCache implements Cache {
@Override
public Object getObject(Object key) {
return cache.get(key);
}
}
//装饰者
public class BlockingCache implements Cache {
@Override
public Object getObject(Object key) {
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
releaseLock(key);
}
return value;
}
}
//示例
public class Example {
public static void main() {
Cache cache = new PerpetualCache();
//装饰缓存,增加锁的功能
cache = new BlockingCache(cache);
CacheKey cacheKey = new CacheKey();
cache.getOject(cacheKey);
}
}
CacheKey
由于mybati支持动态sql,因此不能通过一个简单的string来构造缓存的key,CacheKey就是Cache中的key。
//重写了Equals与hashCode方法
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
//multiplier的默认值
private static final int DEFAULT_MULTIPLYER = 37;
//hashcode的默认值
private static final int DEFAULT_HASHCODE = 17;
//用来计算hashcode值
private final int multiplier;
private int hashcode;
private long checksum;
//对象的个数
private int count;
//存储构成CacheKey的对象
private transient 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();
}
//存储构成CacheKey的对象,以及重新计算hashcode
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
//重新计算hashcode
hashcode = multiplier * hashcode + baseHashCode;
//存储对象
updateList.add(object);
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) {
return false;
}
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 (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
@Override
public String toString() {
StringBuilder returnValue = new StringBuilder().append(hashcode).append(':').append(checksum);
for (Object object : updateList) {
returnValue.append(':').append(ArrayUtil.toString(object));
}
return returnValue.toString();
}
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<Object>(updateList);
return clonedCacheKey;
}
}
CacheKey是由以下5个部分构成的
1.MappedStatement的id
2.指定查询结果的范围也就是rowBounds.Offset,rowBounds.limit
3.查询所使用的包含?的动态sql
4.用户传递的实际参数
5.配置的环境id,如果mybatis-config.xml有配置环境信息的话。
public abstract class BaseExecutor implements Executor {
//创建CacheKey
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
//MappedStatement的id
cacheKey.update(ms.getId());
// 指定查询结果的范围也就是rowBounds.Offset,rowBounds.limit
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) {
// issue #176
// 配置的环境id,如果mybatis-config.xml有配置环境信息的话。
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
}
总结
1.mybatis中存在一级(默认开启)、二级缓存。缓存可以加快查询速度,减少连接db的次数。
2.cache模块属于mybatis的基础支持层。位于org.apache.ibatis.cache包下。
3.PerpetualCache缓存的基础实现(实现了基本的缓存功能,底层数据结构使用HashMap)
4.BlockingCache是带有阻塞功能的缓存装饰器。
5.缓存模块的设计采用了装饰者模式
6.Cache是顶层接口,PerpetualCache是基本实现,decorators下的各个类是不同功能的缓存器,通过装饰者模式来实现不同功能的组合。
7.CacheKey是由5个部分构成的:mappedStatement的id、指定查询结果的范围、查询所使用的包含?的动态sql、用户传递的实际参数、配置的环境id