Mybatis cache mechanism source analysis of secondary cache analysis

The introduction

This source code analysis is based on mybatis version 3.5.8.

Cache in MyBatis refers to that when MyBatis executes an SQL query, it will cache the SQL and the corresponding query results under certain conditions. When the same SQL statement is executed again, it is fetched directly from the cache rather than requested to the database. Of course, if there is an update in the middle, the cache will be invalidated.

The cache in MyBatis is divided into level 1 cache and level 2 cache, level 1 cache is also called SqlSession level cache, level 2 cache is also called table level cache. In layman’s terms, level 1 cache is valid for one session, and level 2 cache can share the cache across multiple sessions.

When caching is enabled, the data query execution process is level 2 cache -> Level 1 cache -> database.

In this article, we focus on level 2 caching.

Mybatis cache mechanism source code analysis level cache parsing

The body of the

The configuration for enabling level 2 cache is as follows:

    <setting name="cacheEnabled" value="true"/>
Copy the code

After level-2 Cache is enabled, all operations in a namespace affect the same Cache. That is, level-2 Cache is shared by multiple SQLSessions and is a global variable.

Level 2 caching can also be thought of as table level caching, because we usually define mapper files in code like this:

<? The XML version = "1.0" encoding = "utf-8"? > <! DOCTYPE mapper PUBLIC "- / / mybatis.org//DTD mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > < mapper namespace="com.xxx.yyy.mapper.TestOrderMapper" > <resultMap id="BaseResultMap" type="com.xxx.yyy.model.TestOrder" > <! -- WARNING - @mbggenerated This element is automatically generated by MyBatis Generator, do not modify. This element was generated on Wed Aug 21 08:47:05 CST 2019. --> <id column="id" property="id" jdbcType="BIGINT" /> <result column="order_id" property="orderId" jdbcType="VARCHAR" /> ...Copy the code

A mapper file has a namespace that corresponds to all the add, delete, change and query operations of a table.

Level 2 cache using cache management class is CachingExecutor, its initialization in the org. Apache. The ibatis. Session. Configuration# newExecutor method:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) { ... if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // CahingExecutor decorates a subclass of BaseExecutor if (cacheEnabled) {executor = new CachingExecutor(executor); }...Copy the code

The class diagram is as follows:

You can see that CachingExecutor holds a delegate for Executor, which is the decorator pattern. CachingExecutor decorates its own logic before delegating to the proxy class for execution, which is key to the second-level cache implementation.

Let’s look at the logic using the query method:

@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache ! = null) {flushes the cache. By default, select does not need to be flushed. Update /delete/insert requires flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); List<E> List = (List<E>) Tcm.getobject (cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; Return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }Copy the code

Obtain the cache instance from MappedStatement. If you do not obtain the cache instance from MappedStatement, you can directly access the query logic of level 1 cache through proxy.

FlushCacheIfRequired Determines whether the cache needs to be flushed.

The key here is TCM, can see the cached data from here for, TCM is TransactionalCacheManager instance, is used to implement the cache transaction management. Take a look at:

public class TransactionalCacheManager { private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>(); .Copy the code

TransactionalCacheManager holds a map, the key is the Cache itself, the value is TransactionalCache, a layer of packaging is done to the Cache, this packing is the key to realize the transaction. We’ll look at TransactionalCacheManager put method:

@Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }
Copy the code

EntriesToAddOnCommit is a map,

  private final Map<Object, Object> entriesToAddOnCommit;

Copy the code

And then if I look at the get method,

@Override public Object getObject(Object key) { // issue #116 Object object = delegate.getObject(key); if (object == null) { entriesMissedInCache.add(key); } // issue #146 if (clearOnCommit) { return null; } else { return object; }}Copy the code

It may seem strange that the PUT method is placed in a local map, but the GET method takes data from the delegate, which is the key to transaction caching. When we use the put method to commit data, we just temporarily put the cache into memory. We don’t actually make the cache work. So when does it come into effect?

Let’s look at the TransactionalCache commit method,

public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }

Copy the code

Then look at the flushPendingEntries method,

private void flushPendingEntries() { 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); }}}Copy the code

See, a cache commit is triggered only when the commit method is called. When is the commit method called? If you follow the link to find, will find that in the end, the caller is: org. Apache. Ibatis. Session. Defaults. DefaultSqlSession# commit (Boolean). This is eventually called by the COMMIT method of the SQL session.

Finally, if the list obtained is empty, the proxy class is used to enter the level 1 cache process, which was explained in the previous article.


reference

  • Tech.meituan.com/2018/01/19/…