mybatis缓存如何设计呢?
- mybaits如何进行细粒度锁的控制
- 如何生存key的。
- 如何存入缓存
带着问题我们一步一步的分析源码
包结构
org.apache.ibatis.cache.Cache 这个接口是如何放入读、写、删操作 类似于数据库的curd操作
public interface Cache {
/**
* @return The identifier of this cache
*/
String getId();
/**
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Object value);
/**
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
/**
* As of 3.3.0 this method is only called during a rollback
* for any previous value that was missing in the cache.
* This lets any blocking cache to release the lock that
* may have previously put on the key.
* A blocking cache puts a lock when a value is null
* and releases it when the value is back again.
* This way other threads will wait for the value to be
* available instead of hitting the database.
*
*
* @param key The key
* @return Not used
*/
Object removeObject(Object key);
/**
* Clears this cache instance.
*/
void clear();
/**
* Optional. This method is not called by the core.
*
* @return The number of elements stored in the cache (not its capacity).
*/
int getSize();
/**
* Optional. As of 3.2.6 this method is no longer called by the core.
* <p>
* Any locking needed by the cache must be provided internally by the cache provider.
*
* @return A ReadWriteLock
*/
ReadWriteLock getReadWriteLock();
}
org.apache.ibatis.cache.CacheKey
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier; //比列因子
private int hashcode; //hashcode值
private long checksum; //计算和
private int count; //计算条数
// 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this is not always true and thus should not be marked transient.
private List<Object> updateList; // 缓存数据
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<>();
}
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
public void update(Object object) {
// 计算hashcode值
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
// 统计和
checksum += baseHashCode;
// baseHashCode = baseHashCode * count
baseHashCode *= count;
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() {
StringJoiner returnValue = new StringJoiner(":");
returnValue.add(String.valueOf(hashcode));
returnValue.add(String.valueOf(checksum));
updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);
return returnValue.toString();
}
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<>(updateList);
return clonedCacheKey;
}
}
org.apache.ibatis.cache.decorators.BlockingCache
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<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
acquireLock(key);
Object value = delegate.getObject(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;
}
private ReentrantLock getLockForKey(Object key) {
return locks.computeIfAbsent(key, k -> new ReentrantLock());
}
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;
}
}
org.apache.ibatis.cache.impl.PerpetualCache
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
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();
}
}
下面以mybatis提供的缓存测试代码来分析缓存的使用。结合源码
org.apache.ibatis.cache.CacheKeyTest#shouldTestCacheKeysEqual
void shouldTestCacheKeysEqual() {
Date date = new Date();
CacheKey key1 = new CacheKey(new Object[] { 1, "hello", null, new Date(date.getTime()) });
CacheKey key2 = new CacheKey(new Object[] { 1, "hello", null, new Date(date.getTime()) });
assertTrue(key1.equals(key2));
assertTrue(key2.equals(key1));
assertTrue(key1.hashCode() == key2.hashCode());
assertTrue(key1.toString().equals(key2.toString()));
}
org.apache.ibatis.cache.CacheKey#CacheKey()
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
org.apache.ibatis.cache.CacheKey#CacheKey()
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<>();
}
org.apache.ibatis.cache.CacheKey#updateAll
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
org.apache.ibatis.cache.CacheKey#update
public void update(Object object) {
// 计算hashcode值
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
// 统计和
checksum += baseHashCode;
// baseHashCode = baseHashCode * count
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
org.apache.ibatis.reflection.ArrayUtil#hashCode
/**
* Returns a hash code for {@code obj}.
*
* @param obj
* The object to get a hash code for. May be an array or <code>null</code>.
* @return A hash code of {@code obj} or 0 if {@code obj} is <code>null</code>
*/
public static int hashCode(Object obj) {
if (obj == null) {
// for consistency with Arrays#hashCode() and Objects#hashCode()
return 0;
}
final Class<?> clazz = obj.getClass();
if (!clazz.isArray()) {
return obj.hashCode();
}
final Class<?> componentType = clazz.getComponentType();
if (long.class.equals(componentType)) {
return Arrays.hashCode((long[]) obj);
} else if (int.class.equals(componentType)) {
return Arrays.hashCode((int[]) obj);
} else if (short.class.equals(componentType)) {
return Arrays.hashCode((short[]) obj);
} else if (char.class.equals(componentType)) {
return Arrays.hashCode((char[]) obj);
} else if (byte.class.equals(componentType)) {
return Arrays.hashCode((byte[]) obj);
} else if (boolean.class.equals(componentType)) {
return Arrays.hashCode((boolean[]) obj);
} else if (float.class.equals(componentType)) {
return Arrays.hashCode((float[]) obj);
} else if (double.class.equals(componentType)) {
return Arrays.hashCode((double[]) obj);
} else {
return Arrays.hashCode((Object[]) obj);
}
}
总结:
这个两个为什么会相等呢?
1、 assertTrue(key1.equals(key2)、 assertTrue(key2.equals(key1));
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;
}
2、assertTrue(key1.hashCode() == key2.hashCode());
// hashcode肯定相等看了上面的源码就知道 这个需要知道hashcode是怎么生成。
3、assertTrue(key1.toString().equals(key2.toString()));
把所传入的值拼接起来
public String toString() {
StringJoiner returnValue = new StringJoiner(":");
// 添加
returnValue.add(String.valueOf(hashcode));
returnValue.add(String.valueOf(checksum));
updateList.stream().map(ArrayUtil::toString).forEach(returnValue::add);
return returnValue.toString();
}