Cache module of mybatis
There are L1 (enabled by default) and L2 caches in mybatis. Caching can speed up queries and reduce the number of connections to db.
The cache module belongs to the basic support layer of mybatis. At org apache. ibatis. Cache package.
--------org.apache.ibatis.cache -------------------------------decorators -----------------------------------------BlockingCache -----------------------------------------FifoCache -----------------------------------------LoggingCache -----------------------------------------LruCache -----------------------------------------ScheduledCache -----------------------------------------SerializedCache -----------------------------------------SoftCache -----------------------------------------SynchronizedCache -----------------------------------------TransactionalCache -----------------------------------------WeakCache -------------------------------impl -----------------------------------PerpetualCache ------------------------------Cache
Cache
Cached top-level interface
public interface Cache { // Get cached id String getId(); // Insert cache void putObject(Object key, Object value); // Query cache Object getObject(Object key); // Delete cache Object removeObject(Object key); //Clean up this cache instance void clear(); // This method is optional, not the core calling method. Query the number of cached key s int getSize(); // ReadWriteLock getReadWriteLock(); }
PerpetualCache
Basic implementation of caching (the basic caching function is realized, and the underlying data structure uses HashMap)
The L1 cache is a session level cache (SqlSession).
public class PerpetualCache implements Cache { private final String id; // The underlying data structure of storage cache uses hashMap private Map<Object, Object> cache = new HashMap<Object, Object>(); // afferent public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } // Insert cache @Override public void putObject(Object key, Object value) { cache.put(key, value); } //Query cache @Override public Object getObject(Object key) { return cache.get(key); } // Remove cache @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 is a cache decorator with blocking function.
public class BlockingCache implements Cache { //Timeout private long timeout; // Decorated cache private final Cache delegate; // Each Key has a reentrant lock private final ConcurrentHashMap<Object, ReentrantLock> locks; //Constructor to decorate other cache objects 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(); } // Insert the cache of the Key and release the lock at the same time @Override public void putObject(Object key, Object value) { try { delegate.putObject(key, value); } finally { releaseLock(key); } } // Query the cache of key @Override public Object getObject(Object key) { //Get lock of key acquireLock(key); // Query the value of key cache Object value = delegate.getObject(key); //If value is not null, release the lock and return the value. Otherwise, keep holding the lock until you know that the key has a value inserted into the cache 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; } //Query the lock held by the Key. If not, create a new lock 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; } }
Thread a queries the key for the first time, and there is no cache. Thread a holds the key lock; At the same time, thread B queries the key. At this time, thread B is blocked. A queries the data, obtains the data cache, releases the key lock, and B is awakened to continue querying from the cache.
BlockingCache will decorate the perpetual cache cache.
Decorator mode
From the package structure of the above Cache, we can see that Cache is the top-level interface, perpetual Cache is the basic implementation, and various classes under decorators are buffers with different functions, which can realize the combination of different functions through decorator mode.
//Top survey interface public interface Cache{ Object getObject(Object key); } //Basic implementation class public class PerpetualCache implements Cache { @Override public Object getObject(Object key) { return cache.get(key); } } //Decorator 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; } } //Example public class Example { public static void main() { Cache cache = new PerpetualCache(); //Decorate the cache and add the function of lock cache = new BlockingCache(cache); CacheKey cacheKey = new CacheKey(); cache.getOject(cacheKey); } }
CacheKey
Since mybati supports dynamic sql, you cannot construct a cached key through a simple string. The cachekey is the key in the Cache.
//Override the Equals and hashCode methods public class CacheKey implements Cloneable, Serializable { private static final long serialVersionUID = 1146682552656046210L; public static final CacheKey NULL_CACHE_KEY = new NullCacheKey(); //Default value for multiplier private static final int DEFAULT_MULTIPLYER = 37; //Default value for hashcode private static final int DEFAULT_HASHCODE = 17; //Used to calculate the hashcode value private final int multiplier; private int hashcode; private long checksum; //Number of objects private int count; //Stores the objects that make up the 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(); } //Store the objects that make up the CacheKey and recalculate the hashcode public void update(Object object) { int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; checksum += baseHashCode; baseHashCode *= count; //Recalculate hashcode hashcode = multiplier * hashcode + baseHashCode; //Storage object 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; } }
The CacheKey consists of the following five parts
1. id of mappedstatement
2. Specify the range of query results, that is, rowboundaries Offset,rowBounds. limit
3. The query uses include? Dynamic sql
4. Actual parameters passed by the user
5. The configured environment id, if mybatis config XML has configuration environment information.
public abstract class BaseExecutor implements Executor { //Create 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(); //id of MappedStatement cacheKey.update(ms.getId()); // Specify the range of query results, that is, rowboundaries Offset,rowBounds. limit cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); // The query used contains? Dynamic 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); } // Actual parameters passed by the user cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 // Configured environment id, if mybatis config XML has configuration environment information. cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; } }
summary
1. There are L1 (enabled by default) and L2 caches in mybatis. Caching can speed up queries and reduce the number of connections to db.
2. The cache module belongs to the basic support layer of mybatis. At org apache. ibatis. Cache package.
3. Basic implementation of perpetualcache cache (the basic cache function is realized, and the underlying data structure uses HashMap)
4.BlockingCache is a cache decorator with blocking function.
5. The design of cache module adopts decorator mode
6.Cache is the top-level interface, and perpetual cache is the basic implementation. Various classes under decorators are buffers with different functions, and the combination of different functions is realized through decorator mode.
7. The cachekey consists of five parts: the id of the mappedStatement, the specified range of query results, and the content used by the query? Dynamic sql, actual parameters passed by the user, and configured environment id