1. Introduction to Cache classes
Interpretation of the Cache before we need to know about the Cache Interface and implementation MyBatis defines a org. Apache. Ibatis. Cache. The Cache Interface as its Cache provider of SPI (ServiceProvider Interface), All MyBatis internal caches should implement this interface
In the implementation class of Cache, Cache has different functions, and each function is independent and does not affect each other. Therefore, for different Cache functions, decorator mode is used here.
Take a look at the cache implementation class as shown below:
1.FIFOCache: a first-in, first-out (FIFO) recycle strategy, decorates the class, maintains a queue internally to ensure that the FIFO, once exceeding the specified size, retrieves the Key from the queue and removes the Key/value pair from the wrapped Cache. 2.LoggingCache: Outputs logs about cache hits. If the DEBUG mode is enabled, logs about hit ratio are generated. LruCache: least recently used algorithm, the Cache recovery strategy, internally to save a LinkedHashMap4 ScheduledCache: time to empty the Cache, but did not start a timer task, but in the use of the Cache, to check the time. 5.SerializedCache: Serialization function that stores values in the cache after serialization. This feature is used by the cache to return a Copy of an instance for thread-safe storage. SynchronizedCache: a cache management strategy based on soft references.Soft references are collected by the garbage collector only when memory runs out. 8.PerpetualCache, once stored, PerpetualCache, inside is a HashMap9.WeakCache: 10.TransactionalCache: a transaction cache that stores multiple caches at once and removes multiple caches 11.BlockingCache: a cache that can blockCopy the code
Two: Level-2 cache initialization
Mybatis is disabled for level 2 cache by default. Level 1 cache is enabled by default. If you need to enable level 2 cache, just add it in Mapper
How is level 2 cache initialized?
We in the previous article (Mybatis source analysis SqlSessionFactory (a)) analysis of the configuration file load, we go back there to find the second level cache load place, I said at the beginning of the “if you need to open just add mapper
XMLConfigBuilder.parse-->parseConfiguration(XNode root)-->mapperElement(root.evalNode("mappers"))-->mapperElement(XNode parent)
Copy the code
Look at the mapperElement method
private void mapperElement(XNode parent) throws Exception {if (parent ! = ) {for (XNode child : parent.getChildren) {if ("package".equals(child.getName)) {String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else {String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource ! = && url == && mapperClass == ) {ErrorContext.instance.resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments); mapperParser.parse; } else if (resource == && url ! = && mapperClass == ) {ErrorContext.instance.resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments); mapperParser.parse; } else if (resource == && url == && mapperClass ! = ) {Class mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); }}}}}Copy the code
At this point I’ve found the Mapper node, and we’re looking ahead
XMLMapperBuilder.mapperParser.parse
Copy the code
The following code
public void parse {if (! configuration.isResourceLoaded(resource)) {configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace; }parsePendingResultMaps; parsePendingChacheRefs; parsePendingStatements; }Copy the code
See configurationElement (parser. EvalNode (“/mapper “)); You see the Mapper node. Keep going
private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace"); if (namespace == || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty"); }builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("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); }}Copy the code
CacheElement (context.evalNode(“cache”));
Look at the source code
private void cacheElement(XNode context) throws Exception {if (context ! = ) {String type = context.getStringAttribute("type", "PERPETUAL"); Class typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction", "LRU"); Class 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); }}Copy the code
Finally found the cacheElement read, here builderAssistant useNewCache built a level 2 cache object
public Cache useNewCache(Class typeClass,Class evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {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 ; configuration.addCache(cache); currentCache = cache; return cache; }Copy the code
Add the configuration object configuration.a
Cache(Cache), see a above code
Decorator(valueOrDefault(evictionClass, LruCache. Class))
The resulting object is the SynchronizedCache which is found in build, which is the interior design pattern.
Three: Cache data search
We learned from previous articles that Executor is the ultimate interface for executing queries and that it has two implementation classes: a BaseExecutor and a CachingExecutor.
Let me take a look at the Query query method in the CachingExecutor implementation class
@Overridepublic List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {Cache cache = ms.getCache; // If (cache! = ) {flushCacheIfRequired(ms); if (ms.isUseCache && resultHandler == ) {ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked")List list = (List) tcm.getObject(cache, key); If (list ==) {DBlist = delegate.query (ms, parameterObject, rowBounds, resultHandler, key); boundSql); tcm.putObject(cache, key, list); }return list; // issue #578 and #116 }}return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }Copy the code
Here we look at an example as shown below
It turns out that our level 2 cache is not working. Why? Let’s take a look at the source code
If you look closely at the Query code for CachingExecutor, TCM is used to find the TransactionalCache and then take getObject when querying the secondary cache. That’s the problem, but we’re going to query the same thing three times, and the first time we look in the database, needless to say, the second time we’re going to determine that level 2 cache has it. The first query finished there is such a sentence.
tcm.putObject(cache, key, list);
Follow along:
getTransactionalCache(cache).putObject(key, value);
Copy the code
GetTransactionalCache (cache) returns the TransactionalCache object and then calls its PUT. What is that
@Override public void putObject(Object key, Object object) {entriesToRemoveOnCommit.remove(key); entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object)); }Copy the code
Encapsulate the cache in an A
Entry object.
Instead of saving data to TransactionalCache, the PUT method saves the cache to entriesToA
OnCommit; Then the entriesToA
What’s OnCommit for?
Look at the name to see that it is needed when committing a transaction. A method is executed, a transaction is committed, a session is committed, and the commit is called in layers. The final call to CachingExecutor is commit:
public void commit(boolean required) throws SQLException { delegate.commit(required); tcm.commit; }Copy the code
TCM commit:
public void commit { for (TransactionalCache txCache : transactionalCaches.values) {txCache.commit; }}Copy the code
Submit all TransactionalCache,
public void commit { if (clearOnCommit) {delegate.clear; } else {for (RemoveEntry entry : entriesToRemoveOnCommit.values) {entry.commit; }}for (AddEntry entry : entriesToAddOnCommit.values) {entry.commit; }reset; }Copy the code
A
Commit method for Entry:
public void commit { cache.putObject(key, value); }Copy the code
You put the cached data in a level 2 cache.
The summary is:
When a transaction method runs, the result is cached in level 1 cache, but not in level 2 cache. The transaction cache only holds references to level 2 cache and data keys and data that need to be cached. When the transaction commits, the transaction cache resets, and the data that was previously stored in the level 2 cache is now in the level 2 cache.
So we repeatedly query in this method, the level 2 cache enabled but failed to hit, can only return the level 1 cache data. The second test opens the transaction, queries it, releases the transaction, and retrieves the transaction query each time. So level 2 cache can hit.
Let’s tweak the code method to put the submission first
Now it’s normal
Four: the order of level 1 and level 2 cache
Level 2 cache — > Level 1 cache — > database
Five: Note the following when using level 2 cache:
There are three things you need to think about when you want to use level 2 caching:
1) The operations and queries on this table are in the same namespace. If operations are performed on other namespaces, data will become obsolete. Because the level 2 cache is based on namespaces, operations in different namespaces do not affect each other.
2) For the query of associated tables, all operations of associated tables must be in the same namespace.
3) Do not operate the database directly, otherwise there will be problems in the data query results
In general, only queries that operate and query in the same namespace can be cached. Queries in other namespaces may have problems.