First from the configuration file, there is a cacheenabled configuration item, when set to True (the default is True), the session will use a cachingexecutor to wrap our executor instance:
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 (Executortyp E.reuse = = executortype) { executor = new Reuseexecutor (this, transaction); } else { executor = new Simpleexec Utor (this, transaction); } if (cacheenabled) { executor = new Cachingexecutor (executor); } Executor = (executor) interceptorchain.pluginall (executor); return executor; }
This is a decorator mode, in most cases directly forwarded calls, in the Update method and the query method according to the configuration of statement in Mapper to read and refresh the cache
@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, Resultha Ndler 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); }
Where both flushcacheifrequired and Ms.isusecache are configured in the mapper file to flush the cache and use the cache
The above-mentioned caches are level two caches, so-called level two caches are global caches that can be manipulated at the end of a query.
Compared to the global cache, then there is a local cache, also known as a first-level cache, in MyBatis with LocalCache representation
The local cache for the
MyBatis can be configured in two scopes: session and statement, conversation scope, and statement scope; The local cache is always on, Inside the executor, a member variable localcache of type Perpetualcache is stored as the query cache, a Perpetualcache-type Localoutputparametercache member to cache the stored procedure's output parameters. If it is a statement level cache, then the cache will be emptied after this statement execution is complete. If the update operation is performed, the local cache is emptied immediately.
@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//If it is a statement level cache, then the cache will be emptied after this statement execution is complete Clearlocalcache ();}} return list; }//empties the cache @Override public void Clearlocalcache () {if (!closed) {localcache.clear (); Localoutputparametercache.clear (); }} private <E> list<e> queryfromdatabase (mappedstatement MS, Object parameter, rowbounds rowbounds, Resulth Andler 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); }//Cache query Results Localcache.putobject (key, list);if (ms.getstatementtype () = = statementtype.callable) {//cache stored procedure output parameter Localoutputparametercache.putobject (key, parameter);} return list; } @Override public int update (mappedstatement ms, Object parameter) throws SQLException {errorcontext.instance (). Res Ource (Ms.getresource ()). Activity ("Executing an Update"). Object (Ms.getid ()); if (closed) {throw new Executorexception ("Executor was closed."); }//Perform an update operation to immediately empty the local cache Clearlocalcache ();Return DoUpdate (MS, parameter); }
As we can see, the local cache uses Perpetualcache, which implements the cache interface, so what is the level two cache used for:
In fact, this is another decorator mode, the only real implementation of the cache interface is Perpetualcache, and the other implementation classes are wrappers on it, such as Loggingcache:
public class Loggingcache implements Cache { private log log; Private Cache delegate; protected int requests = 0; Number of accesses protected int hits = 0; Hit Quantity 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 ()); Record cache Hit rate } return value ; @Override Public Object Removeobject (Object key) { return delegate.removeobject (key); }
Cache Hit ratio private double gethitratio () { return (double) hits/(double) requests; }}
Loggingcache is a wrapper to the cache object and then records the cache hit ratio.
Of course, there are packaging for elimination, such as LruCache (the default elimination wrapper):
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); }//Set cache size, allocate cache storage, Linkedhashmap, remove key to Eldestkey when least recently usedpublic 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, update timereturn Delegate.getobject (key); } @Override public void Clear () {delegate.clear (); Keymap.clear (); } @Override Public Readwritelock Getreadwritelock () {return null; }//Removal of Eldestkeyprivate void Cyclekeylist (Object key) {Keymap.put (key, key); if (Eldestkey! = null) {delegate.removeobject (Eldestkey); Eldestkey = null; } }}
Similarly, the implementation of this cache and the obsolete cache can be specified in the mapper file, you can write your own cache implementation class, and then configure in the mapper file
private void Cacheelement (XNode context) throws Exception { if (context! = null) { String type = context.getst Ringattribute ("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); } }
But these do not seem to have anything to do with executor, our cachingexecutor is to use the TCM member object to manage the cache, this TCM is actually an example of Transactionalcachemanager, The Transactionalcachemanager class is a transactional wrapper over the cache, and if a transaction fails, what about the cache, and how does the cache that was previously written roll back?
This is accomplished using Transactionalcache in Transactionalcachemanager, which is also a layer wrapper on the cache, but in the Putobject method, Instead of calling the putobject of the wrapped cache object directly, it saves the key-value pair to its own private member, Entriestoaddoncommit, when the commit method is called. These key-value pairs will be brushed into the true cache by invoking the putobject of the wrapped object cache:
Private Map<object, object> Entriestoaddoncommit; Internal key value to public void PutObject (Object key, Object object) { entriestoaddoncommit.put (key, object); Put to internal key-value pair } public void commit () { if (clearoncommit) { delegate.clear (); } Flushpendingentries (); Reset (); } public void rollback () { unlockmissedentries (); Reset (); } private void Flushpendingentries () { //The internal key-value pairs are brushed into the real 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 the cache as a Transactionalcache
Private Transactionalcache Gettransactionalcache (cache cache) { Transactionalcache Txcache = Transactionalcaches.get (cache); if (Txcache = = null) { Txcache = new Transactionalcache (cache); Transactionalcaches.put (cache, Txcache); } return txcache; }
After that, take a look at the calculation of the cache key, CacheKey is calculated in the Baseexecutor.createcachekey method:
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 ());//StatementidCachekey.update (Rowbounds.getoffset ());//OffsetCachekey.update (Rowbounds.getlimit ());//LimitCachekey.update (Boundsql.getsql ());//SQL statementslist<parametermapping> parametermappings = Boundsql.getparametermappings (); Typehandlerregistry typehandlerregistry = Ms.getconfiguration (). Gettypehandlerregistry (); Mimic Defaultparameterhandler logic for (parametermapping parametermapping:parametermappings) {if (parameter Mapping.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);//Parameters}} if (configuration.getenvironment () = null) {//Issue #176 Cachekey.update (Configuration.getenvironme NT (). GetId ());//Database} return CacheKey; }
CacheKey is calculated based on Statementid, paging, query parameters, and database links as criteria, and CacheKey distinguishes each cachekey according to these conditions:
public class CacheKey implements Cloneable, Serializable {//Hashcode extension factor private static final int default_multiplyer = 37; Hashcode Initial value 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 = A Rray.getlength (object); for (int i = 0; i < length; i++) {Object element = Array.get (object, i); DoUpdate (Element); }} else {DoUpdate (object); } }//Calculate hashcode and checksumprivate 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 value is different, certainly not the same cache if (hashcode! = Cachekey.hashcode) {return false; }//checksum is different, nor is the same as the cache if (checksum! = Cachekey.checksum) {return false; }//The number of parameters is different if (count! = Cachekey.count) {return false; }//And then compare each condition in turn, for Speedfor (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; }
//Generate Cahcekey strings based on conditions, slow efficiency, use for custom caches@Override public String toString () {StringBuilder returnvalue = new StringBuilder (). Append (Hashcode). Append (': '). AppE nd (checksum); For (object object:updatelist) {returnvalue.append (': '). Append (object); } return returnvalue.tostring (); }}
Finally, attach a sequence diagram of the cache work, Transactioncachingmanager is a level two cache, LocalCache is a first-level cache
MyBatis and caching