First, existence is reasonable

MyBatis in order to improve our query performance, specially designed for level 1 cache and the second level cache, as is known to all, we in the development environment, use the cache, also will encounter all sorts of challenges, such as the cache penetrate, cache avalanche, dirty reads data and so on all sorts of problems, MyBatis, too, in the design of the second level cache, MyBatis also encountered various challenges;

When I was watching the design of MyBatis for the second-level cache these days, I suddenly found that a data we queried was not directly placed in the second-level cache, but in another storage space, and it would be set into the second-level cache only after it was submitted. I was not only confused, but reasonable as it exists. Why did MyBatis “reinvent the wheel” when designing the level 2 cache? So there is the author stayed up late to explore the process!

2. Test code

First, for testing purposes, let’s make an example code that can hit the level 2 cache:

@Test
public void sessionTest(a){
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE, true);
    List<Object> objects = sqlSession.selectList("com.huangfu.TestMapper.selectUser"."Saturday");
    List<Object> objects1 = sqlSession.selectList("com.huangfu.TestMapper.selectUser"."Saturday");
 // Oh ho submit a ha  sqlSession.commit();  List<Object> objects2 = sqlSession.selectList("com.huangfu.TestMapper.selectUser"."Saturday"); } Copy the code

Note: As mentioned above, the cache will only be refreshed to level 2 cache space after the commit. The principle will be explored later.

How many hits are we going to get here? Did you guess twice? If you guessed twice, then you don’t understand the concept of the temporary area. In fact, after the first query, the results of the query are not synchronized to the second level cache space, but are only refreshed after the commit, so the correct answer is only one hit, with a hit ratio of 0.3333333333333333

As for this reason, listen to the author:

Third, search for truth

The first thing you need to understand is this concept: The staging area is the cached data that the SqlSession needs to commit to a secondary cache during the transaction. Because the data may be rollback during the transaction, the data cannot be directly committed to the secondary cache. Instead, the data is temporarily stored in the TransactionalCache. After the transaction is committed, the data stored in the transaction is committed to the level 2 cache. If the transaction is rolled back, the data is cleared!

The staging area can be understood as an intermediate container, which is to ensure the atomality of a transaction. It stores all the data before the commit operation. After the commit operation is executed, the contents of the staging area will be refreshed to the second level cache space at one time!

In the previous MyBatis article I mentioned that the logic for the level 2 cache is abstracted into the CachingExecutor. Now that we have the level 2 cache enabled, the session object is: executor = 1: For those of you who have read my previous articles, the query method executes the query method by default, so the object we focus on is the Query method.

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, 
              ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // Obtain the level 2 cache space in this namespace
    Cache cache = ms.getCache();
 if(cache ! =null) {  // Whether the refresh staging area is set  flushCacheIfRequired(ms);  if (ms.isUseCache() && resultHandler == null) {  ensureNoOutParams(ms, boundSql);  @SuppressWarnings("unchecked")  // Query the cached data in the secondary cache space  List<E> list = (List<E>) tcm.getObject(cache, key);  // If there is no data in the level 2 cache space  if (list == null) {  // Query the database  list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  // Put the query data into the staging area  tcm.putObject(cache, key, list);  }  return list;  }  }  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  } Copy the code

As you can see, our interpolated data is not actually placed in the cache, but in the staging area, for reasons that we will discuss later! So when is it flushed from the staging area to the cache? Commit is a commit action. Let’s take a look at the basic logic of commit!

Along the source trace, you will see the following logic

private void flushPendingEntries(a) {
    // Iterate over all the staging data and place it one by one into the secondary cache space
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
. Ignore the code beyond the discussion.... } Copy the code

The reason for the hit is that only after the commit, the query is flushed into the second level cache, so the commit query is hit. So what’s the point?

Just to avoid dirty data, imagine what would happen if there was no staging space.

Suppose there is a write operation, and then another request finds the data and places it in the level 2 cache, but the data is rolled back, then a dirty read will occur!

image-20200710135312612

On the contrary, we can not clear the level 2 cache space directly during the modification operation, but pseudo-clear (save a clear mark), until the commit operation, the real delete operation!

So in the modify method there is this code:

public void clear(Cache cache) {
    // The clear method is called as follows
  getTransactionalCache(cache).clear();
}

public void clear(a) {  // Set the clear flag  clearOnCommit = true;  entriesToAddOnCommit.clear();  } Copy the code

As you can see, the modification method does not actually clear the level 2 cache area, but instead sets a commit flag. What is the use of this commit flag?

public void commit(a) {
    // Delete the level 2 cache when the clear flag is set
    if (clearOnCommit) {
        delegate.clear();
    }
 // Refresh the staging to the cache  flushPendingEntries();  // Restore a numeric position. For example, reset the commit flag to false  reset(); } Copy the code

Why more than one step?

A change operation, after modifying the data, the level 2 cache is cleared, but at this time the data is abnormal, the rollback occurs! In fact, the data has not been modified successfully, we should not have to clear the secondary cache, this should not be! So you can’t clear the cache without committing!

After the above analysis, we conclude the general process as follows:

A temporary storage area, can avoid part of the data dirty read problem, have to sigh the exquisite design of MyBatis! But will this really solve the dirty reading problem? Not so! Here are some extensions to dirty read problems caused by some special reasons!

4. Expand knowledge

Because the MyBatis data level 2 cache is designed to be isolated from different namespaces (a Mapper uses a level 2 cache), dirty read data can still occur under certain circumstances!

The reason for this is that different Mapper query isolates use different storage Spaces. How to solve the dirty read problem when two mappers operate on the same table?

Think about it, what is the reason for this problem? Because there is no common cache, so we can use the same cache to solve the problem! How to use it?

Just reference the namespace of one Mapper to the namespace of another in the corresponding Mapper file to make two mappers share the same cache space!<cache-ref namespace="xxx.xxx.xxx.UserMapper2"></cache-ref>
Copy the code

Of course, there are other solutions, such as the annotation level, the author will not be repeated! In fact, these two days I read some information on the Internet, the author should be the first person to introduce the temporary storage area, if there are problems in the understanding of the article, you are welcome to correct!


If there is an error in the understanding of the article, you are welcome to talk to the bosses. Welcome to follow the author’s public account, progress together, learn together!

– END –