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

dd

Cache(Cache), see a above code

dd

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

dd

Entry object.

Instead of saving data to TransactionalCache, the PUT method saves the cache to entriesToA

dd

OnCommit; Then the entriesToA

dd

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

dd

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.