Learn more about MyBatis second-level cache and mybatis
Learn more about MyBatis second-level Cache 1. Complete Cache creation process
We start with parsing the mybatis-config.xml configuration file from SqlSessionFactoryBuilder:
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
Then:
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());
See the parser. parse () method:
parseConfiguration(parser.evalNode("/configuration"));
View the location where the Mapper. xml file is processed:
mapperElement(root.evalNode("mappers"));
View the XMLMapperBuilder that processes er. xml:
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();
Continue to read the parse method:
configurationElement(parser.evalNode("/mapper"));
Here:
String namespace = context.getStringAttribute("namespace");if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));
Seenamespace
Is in xml<mapper>
The attribute of the element. Then, the following are processed successively.cache-ref
Andcache
, Followedcache
Will overwrite the previouscache-ref
But ifcache-ref
Reference not foundcache
Will not be overwritten until the final processing is completed.cache
Insteadcache-ref
Overwrite. Is it a bit dizzy and messy? Therefore, do not configure the two at the same time. In fact, few will do the same.
Let's see how MyBatis works.<cache/>
:
private void cacheElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("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); }}
From the source code, we can see that MyBatis reads those attributes and can easily reach the default values of these attributes.
The method for creating a cache object for Java isbuilderAssistant.useNewCache
Let's take a look at this Code:
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { typeClass = valueOrDefault(typeClass, PerpetualCache.class); evictionClass = valueOrDefault(evictionClass, LruCache.class); Cache cache = new CacheBuilder(currentNamespace) .implementation(typeClass) .addDecorator(evictionClass) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); configuration.addCache(cache); currentCache = cache; return cache;}
From the point where this method is called, we can see that the returned value cache is not used, and the MappedStatement is used in the subsequent process.currentCache
.
Ii. Use the Cache Process
In the systemCachingExecutor
Medium:
@Overridepublic <E> List<E> query( MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache();
After obtaining the cache, first determine whether there is a secondary cache.
Only pass<cache/>,<cache-ref/>
Or@CacheNamespace,@CacheNamespaceRef
The er. xml or Mapper interface marked to use the cache (the same namespace cannot be used at the same time) will have a second-level cache.
if (cache != null) {
If the cache exists (<insert>,<select>,<update>,<delete>
OfflushCache
Attribute to determine whether to clear the cache.
flushCacheIfRequired(ms);
Then, according to the xml configuration attributesuseCache
To determine whether to use the cache (resultHandler generally uses the default value, rarely null ).
if (ms.isUseCache() && resultHandler == null) {
Make sure that the method does not have an Out parameter. mybatis does not support the cache of stored procedures. Therefore, if it is a stored procedure, an error is reported here.
ensureNoOutParams(ms, parameterObject, boundSql);
If no problem exists, it will take the value from the cache based on the key:
@SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key);
If no cache is available, the query is executed and the query results are cached.
if (list == null) { list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 }
Returned results
return list; } }
If no cache is available, the query is executed directly.
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}
In the above Codetcm.putObject(cache, key, list);
The result is cached. However, MyBatis is saved to a Map (default cache configuration) in serialized form until the sqlsession is closed.
Iii. Considerations for Using Cache 1. You can only use Cache on a table with only one table operation
Not only do you want to ensure that this table only has single table operations in the system, but all the operations related to this table must be in onenamespace
.
2. Use the cache when the query is far greater than the insert, update, and delete operations.
We should be clear about this.Remember, this must be done on the premise of 1!
4. Avoid using second-level cache
There may be many people who do not understand this. The benefits of level-2 cache are far inferior to those hidden dangers.
Why avoid using second-level cache?
There is no harm in complying with the [Considerations for Cache Usage.
In other cases, there will be a lot of harm.
Some operations on a table are not independent of the table.
namespace
.
For exampleUserMapper.xml
Most of them targetuser
Table operations. HoweverXXXMapper.xml
Foruser
Single Table operations.
This causesuser
The data in the two namespaces is inconsistent. IfUserMapper.xml
InXXXMapper.xml
The cache is still valid.user
The cache results for single-table queries may be incorrect.
The more dangerous situation is thatXXXMapper.xml
When insert, update, and delete operations are performedUserMapper.xml
The various operations in are full of unknown and risks.
Operations on such a single table may be uncommon. But you may think of a common situation.
Multi-Table operations cannot use the cache
Why not?
First, no matter what the multi-table operation writesnamespace
A table is not in thisnamespace
.
For example, two tables:role
Anduser_role
If I want to query all roles of a userrole
Multi-Table operations will be involved.
<select id="selectUserRoles" resultType="UserRoleVO"> select * from user_role a,role b where a.roleid = b.roleid and a.userid = #{userid}</select>
Like the above query, you will writeXml??
Whether it is writtenRoleMapper.xml
OrUserRoleMapper.xml
Or an independentXxxMapper.xml
. If the second-level cache is used, the above query results may be incorrect.
If you modify the role of this user, the result is wrong when the above query uses the cache.
This should be easy to understand.
In my opinion, there is no solution in the current cache method of MyBatis. Multi-Table operations cannot be cached at all.
If you want them to use the samenamespace
(Pass<cache-ref>
To avoid dirty data, the meaning of the cache is lost.
We can see that, in fact,Level 2 Cache not available. It is useless to introduce so much in the entire article.
5. How to save the second-level cache?
The second-level cache cannot be used more efficiently.
However, multi-Table operations are avoided.Dirty dataThere is still a solution. The solution is to use the Interceptor to determine which tables are involved in the executed SQL statements (which can be parsed using jsqlparser), and then automatically clear the cache of the relevant tables. However, this method is very inefficient for caching.
Designing such a plug-in is quite complicated. Since I didn't want to implement it, I won't talk nonsense.
Finally, we recommend that you discard the second-level cache and use controllable cache at the business layer to replace it better.
If you have a better solution, leave a message.~~~